mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-13 11:19:25 +00:00
Compare commits
56 Commits
20200909.0
...
fix-action
Author | SHA1 | Date | |
---|---|---|---|
![]() |
42f720496b | ||
![]() |
c64d88d8b5 | ||
![]() |
3030b8d476 | ||
![]() |
92ed14c0e4 | ||
![]() |
5b94a4de9a | ||
![]() |
709112c498 | ||
![]() |
e465ec8835 | ||
![]() |
f6eb31bf9d | ||
![]() |
426f939982 | ||
![]() |
fab6cebf0d | ||
![]() |
ff081dd0f1 | ||
![]() |
868399ed6f | ||
![]() |
1bc9b95289 | ||
![]() |
9af805ab5e | ||
![]() |
6b88081360 | ||
![]() |
50d37ce4f6 | ||
![]() |
af0246cd27 | ||
![]() |
857e4e49d8 | ||
![]() |
c1afed7f98 | ||
![]() |
5480e54185 | ||
![]() |
99d0a0a6fd | ||
![]() |
8a998369d6 | ||
![]() |
8b490c5047 | ||
![]() |
7e70ba6ab2 | ||
![]() |
90e09fc384 | ||
![]() |
266f2e763d | ||
![]() |
c979cfb912 | ||
![]() |
8ee29b1e43 | ||
![]() |
26fbc07cac | ||
![]() |
f01fe65be4 | ||
![]() |
3fdd6a80f9 | ||
![]() |
da1de8db1d | ||
![]() |
dd1bf7b49d | ||
![]() |
f18913b5a0 | ||
![]() |
a2cd227f1a | ||
![]() |
78e64e1f60 | ||
![]() |
23a9b79320 | ||
![]() |
76394ce341 | ||
![]() |
1935df1faa | ||
![]() |
5af4ce28ab | ||
![]() |
ce8ee569c4 | ||
![]() |
b0508f430e | ||
![]() |
2139a80a7a | ||
![]() |
aa4bc2ce03 | ||
![]() |
fa65f84e09 | ||
![]() |
c06357a351 | ||
![]() |
092a02a624 | ||
![]() |
b9699f745f | ||
![]() |
7fa9f10c30 | ||
![]() |
7bf0655dae | ||
![]() |
96c5fdcbeb | ||
![]() |
c2e6d40382 | ||
![]() |
810d2a1ceb | ||
![]() |
af74f21af9 | ||
![]() |
cdf7558a8e | ||
![]() |
41b86e6c10 |
60
.github/workflows/codeql-analysis.yml
vendored
Normal file
60
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [dev]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['javascript']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
@@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
import "../../../src/state-summary/state-card-content";
|
||||
import "./more-info-content";
|
||||
|
||||
class DemoMoreInfo extends PolymerElement {
|
||||
static get template() {
|
||||
|
73
gallery/src/components/more-info-content.ts
Normal file
73
gallery/src/components/more-info-content.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { property, PropertyValues, UpdatingElement } from "lit-element";
|
||||
import dynamicContentUpdater from "../../../src/common/dom/dynamic_content_updater";
|
||||
import { stateMoreInfoType } from "../../../src/common/entity/state_more_info_type";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-alarm_control_panel";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-automation";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-camera";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-climate";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-configurator";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-counter";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-cover";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-default";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-fan";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-group";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-humidifier";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-input_datetime";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-light";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-lock";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-media_player";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-person";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-script";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-sun";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-timer";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-vacuum";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-water_heater";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-weather";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
class MoreInfoContent extends UpdatingElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
private _detachedChild?: ChildNode;
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.style.position = "relative";
|
||||
this.style.display = "block";
|
||||
}
|
||||
|
||||
// This is not a lit element, but an updating element, so we implement update
|
||||
protected update(changedProps: PropertyValues): void {
|
||||
super.update(changedProps);
|
||||
const stateObj = this.stateObj;
|
||||
const hass = this.hass;
|
||||
|
||||
if (!stateObj || !hass) {
|
||||
if (this.lastChild) {
|
||||
this._detachedChild = this.lastChild;
|
||||
// Detach child to prevent it from doing work.
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._detachedChild) {
|
||||
this.appendChild(this._detachedChild);
|
||||
this._detachedChild = undefined;
|
||||
}
|
||||
|
||||
const moreInfoType =
|
||||
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
|
||||
? stateObj.attributes.custom_ui_more_info
|
||||
: "more-info-" + stateMoreInfoType(stateObj);
|
||||
|
||||
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
|
||||
hass,
|
||||
stateObj,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-content", MoreInfoContent);
|
@@ -3,10 +3,10 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-more-infos";
|
||||
import "../components/more-info-content";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
|
@@ -16,6 +16,7 @@ import "../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
HassioResponse,
|
||||
ignoredStatusCodes,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
@@ -166,7 +167,7 @@ export class HassioUpdate extends LitElement {
|
||||
} catch (err) {
|
||||
// Only show an error if the status code was not expected (user behind proxy)
|
||||
// or no status at all(connection terminated)
|
||||
if (err.status_code && ![502, 503, 504].includes(err.status_code)) {
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Update failed",
|
||||
text: extractApiErrorMessage(err),
|
||||
|
@@ -19,7 +19,10 @@ import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoredStatusCodes,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
@@ -245,10 +248,13 @@ class HassioHostInfo extends LitElement {
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
// Ignore connection errors, these are all expected
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
@@ -272,10 +278,13 @@ class HassioHostInfo extends LitElement {
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
// Ignore connection errors, these are all expected
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200909.0",
|
||||
version="20200912.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -44,7 +44,6 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"script",
|
||||
"sun",
|
||||
"timer",
|
||||
"updater",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
"weather",
|
||||
|
@@ -105,12 +105,12 @@ const processTheme = (
|
||||
const keys = {};
|
||||
for (const key of Object.keys(combinedTheme)) {
|
||||
const prefixedKey = `--${key}`;
|
||||
const value = combinedTheme[key]!;
|
||||
const value = String(combinedTheme[key]!);
|
||||
styles[prefixedKey] = value;
|
||||
keys[prefixedKey] = "";
|
||||
|
||||
// Try to create a rgb value for this key if it is not a var
|
||||
if (!value.startsWith("#")) {
|
||||
if (value.startsWith("#")) {
|
||||
// Can't convert non hex value
|
||||
continue;
|
||||
}
|
||||
|
@@ -3,9 +3,10 @@ import { HomeAssistant } from "../../types";
|
||||
import { DOMAINS_WITH_CARD } from "../const";
|
||||
import { canToggleState } from "./can_toggle_state";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
|
||||
export const stateCardType = (hass: HomeAssistant, stateObj: HassEntity) => {
|
||||
if (stateObj.state === "unavailable") {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
return "display";
|
||||
}
|
||||
|
||||
|
50
src/common/util/throttle.ts
Normal file
50
src/common/util/throttle.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// From: underscore.js https://github.com/jashkenas/underscore/blob/master/underscore.js
|
||||
|
||||
// Returns a function, that, when invoked, will only be triggered at most once
|
||||
// during a given window of time. Normally, the throttled function will run
|
||||
// as much as it can, without ever going more than once per `wait` duration;
|
||||
// but if you'd like to disable the execution on the leading edge, pass
|
||||
// `false for leading`. To disable execution on the trailing edge, ditto.
|
||||
export const throttle = <T extends Function>(
|
||||
func: T,
|
||||
wait: number,
|
||||
leading = true,
|
||||
trailing = true
|
||||
): T => {
|
||||
let timeout: number | undefined;
|
||||
let previous = 0;
|
||||
let context: any;
|
||||
let args: any;
|
||||
const later = () => {
|
||||
previous = leading === false ? 0 : Date.now();
|
||||
timeout = undefined;
|
||||
func.apply(context, args);
|
||||
if (!timeout) {
|
||||
context = null;
|
||||
args = null;
|
||||
}
|
||||
};
|
||||
// @ts-ignore
|
||||
return function (...argmnts) {
|
||||
// @ts-ignore
|
||||
// @typescript-eslint/no-this-alias
|
||||
context = this;
|
||||
args = argmnts;
|
||||
|
||||
const now = Date.now();
|
||||
if (!previous && leading === false) {
|
||||
previous = now;
|
||||
}
|
||||
const remaining = wait - (now - previous);
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
previous = now;
|
||||
func.apply(context, args);
|
||||
} else if (!timeout && trailing !== false) {
|
||||
timeout = window.setTimeout(later, remaining);
|
||||
}
|
||||
};
|
||||
};
|
@@ -6,6 +6,7 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
@@ -51,7 +52,8 @@ const rowRenderer = (
|
||||
root.querySelector("[secondary]")!.textContent = model.item.entity_id;
|
||||
};
|
||||
|
||||
class HaEntityPicker extends LitElement {
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
@@ -276,8 +278,6 @@ class HaEntityPicker extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-entity-picker", HaEntityPicker);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-picker": HaEntityPicker;
|
||||
|
@@ -20,6 +20,7 @@ import { stateIcon } from "../../common/entity/state_icon";
|
||||
import { timerTimeRemaining } from "../../common/entity/timer_time_remaining";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-label-badge";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
|
||||
@customElement("ha-state-label-badge")
|
||||
export class HaStateLabelBadge extends LitElement {
|
||||
@@ -81,7 +82,8 @@ export class HaStateLabelBadge extends LitElement {
|
||||
? ""
|
||||
: this.image
|
||||
? this.image
|
||||
: state.attributes.entity_picture_local || state.attributes.entity_picture}"
|
||||
: state.attributes.entity_picture_local ||
|
||||
state.attributes.entity_picture}"
|
||||
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
|
||||
.description="${this.name ? this.name : computeStateName(state)}"
|
||||
></ha-label-badge>
|
||||
@@ -108,7 +110,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
return null;
|
||||
case "sensor":
|
||||
default:
|
||||
return state.state === "unknown"
|
||||
return state.state === UNKNOWN
|
||||
? "-"
|
||||
: state.attributes.unit_of_measurement
|
||||
? state.state
|
||||
@@ -121,7 +123,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
}
|
||||
|
||||
private _computeIcon(domain: string, state: HassEntity) {
|
||||
if (state.state === "unavailable") {
|
||||
if (state.state === UNAVAILABLE) {
|
||||
return null;
|
||||
}
|
||||
switch (domain) {
|
||||
@@ -166,7 +168,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
|
||||
private _computeLabel(domain, state, _timerTimeRemaining) {
|
||||
if (
|
||||
state.state === "unavailable" ||
|
||||
state.state === UNAVAILABLE ||
|
||||
["device_tracker", "alarm_control_panel", "person"].includes(domain)
|
||||
) {
|
||||
// Localize the state with a special state_badge namespace, which has variations of
|
||||
|
@@ -26,7 +26,11 @@ class HaCameraStream extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj?: CameraEntity;
|
||||
|
||||
@property({ type: Boolean }) public showControls = false;
|
||||
@property({ type: Boolean, attribute: "controls" })
|
||||
public controls = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "muted" })
|
||||
public muted = false;
|
||||
|
||||
// We keep track if we should force MJPEG with a string
|
||||
// that way it automatically resets if we change entity.
|
||||
@@ -56,9 +60,9 @@ class HaCameraStream extends LitElement {
|
||||
? html`
|
||||
<ha-hls-player
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
?controls=${this.showControls}
|
||||
.muted=${this.muted}
|
||||
.controls=${this.controls}
|
||||
.hass=${this.hass}
|
||||
.url=${this._url}
|
||||
></ha-hls-player>
|
||||
|
@@ -97,6 +97,7 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
.CodeMirror {
|
||||
height: var(--code-mirror-height, auto);
|
||||
direction: var(--code-mirror-direction, ltr);
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
max-height: var(--code-mirror-max-height, --code-mirror-height);
|
||||
|
@@ -61,7 +61,7 @@ class HaHLSPlayer extends LitElement {
|
||||
return html`
|
||||
<video
|
||||
?autoplay=${this.autoPlay}
|
||||
?muted=${this.muted}
|
||||
.muted=${this.muted}
|
||||
?playsinline=${this.playsInline}
|
||||
?controls=${this.controls}
|
||||
@loadeddata=${this._elementResized}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
property,
|
||||
SVGTemplateResult,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
@customElement("ha-settings-row")
|
||||
export class HaSettingsRow extends LitElement {
|
||||
@@ -49,6 +49,9 @@ export class HaSettingsRow extends LitElement {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
::slotted(ha-switch) {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,6 @@ 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 { navigate } from "../common/navigate";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { ActionHandlerDetail } from "../data/lovelace";
|
||||
@@ -166,7 +165,7 @@ let sortStyles: CSSResult;
|
||||
class HaSidebar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public alwaysExpand = false;
|
||||
|
||||
@@ -235,7 +234,14 @@ class HaSidebar extends LitElement {
|
||||
</style>
|
||||
`
|
||||
: ""}
|
||||
<div class="menu">
|
||||
<div
|
||||
class="menu"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: !this._editMode,
|
||||
disabled: this._editMode,
|
||||
})}
|
||||
>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
@@ -253,7 +259,7 @@ class HaSidebar extends LitElement {
|
||||
<div class="title">
|
||||
${this._editMode
|
||||
? html`<mwc-button outlined @click=${this._closeEditMode}>
|
||||
DONE
|
||||
${hass.localize("ui.sidebar.done")}
|
||||
</mwc-button>`
|
||||
: "Home Assistant"}
|
||||
</div>
|
||||
@@ -266,11 +272,6 @@ 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._editMode
|
||||
? html`<div id="sortable">
|
||||
@@ -286,27 +287,29 @@ class HaSidebar extends LitElement {
|
||||
? html`
|
||||
${this._hiddenPanels.map((url) => {
|
||||
const panel = this.hass.panels[url];
|
||||
if (!panel) {
|
||||
return "";
|
||||
}
|
||||
return html`<paper-icon-item
|
||||
@click=${this._unhidePanel}
|
||||
class="hidden-panel"
|
||||
.panel=${url}
|
||||
>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${panel.url_path === "lovelace"
|
||||
.icon=${panel.url_path === this.hass.defaultPanel
|
||||
? "mdi:view-dashboard"
|
||||
: panel.icon}
|
||||
></ha-icon>
|
||||
<span class="item-text"
|
||||
>${panel.url_path === "lovelace"
|
||||
>${panel.url_path === this.hass.defaultPanel
|
||||
? 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>
|
||||
<mwc-icon-button class="hide-panel">
|
||||
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-icon-item>`;
|
||||
})}
|
||||
<div class="spacer" disabled></div>
|
||||
@@ -322,7 +325,7 @@ class HaSidebar extends LitElement {
|
||||
)}
|
||||
href="#external-app-configuration"
|
||||
tabindex="-1"
|
||||
@panel-tap=${this._handleExternalAppConfiguration}
|
||||
@click=${this._handleExternalAppConfiguration}
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
@@ -446,6 +449,9 @@ class HaSidebar extends LitElement {
|
||||
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||
this._notifications = notifications;
|
||||
});
|
||||
window.addEventListener("hass-edit-sidebar", () =>
|
||||
this._activateEditMode()
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
@@ -478,12 +484,15 @@ class HaSidebar extends LitElement {
|
||||
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
|
||||
}
|
||||
|
||||
private async _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
|
||||
if (ev.detail.action === "tap") {
|
||||
fireEvent(ev.target as HTMLElement, "panel-tap");
|
||||
private _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
|
||||
if (ev.detail.action !== "hold") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._activateEditMode();
|
||||
}
|
||||
|
||||
private async _activateEditMode() {
|
||||
if (!Sortable) {
|
||||
const [sortableImport, sortStylesImport] = await Promise.all([
|
||||
import("sortablejs/modular/sortable.core.esm"),
|
||||
@@ -498,9 +507,7 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
this._editMode = true;
|
||||
|
||||
if (!this.expanded) {
|
||||
fireEvent(this, "hass-toggle-menu");
|
||||
}
|
||||
fireEvent(this, "hass-open-menu");
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
@@ -539,7 +546,7 @@ class HaSidebar extends LitElement {
|
||||
|
||||
private async _unhidePanel(ev: Event) {
|
||||
ev.preventDefault();
|
||||
const index = this._hiddenPanels.indexOf((ev.target as any).panel);
|
||||
const index = this._hiddenPanels.indexOf((ev.currentTarget as any).panel);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
@@ -651,22 +658,17 @@ class HaSidebar extends LitElement {
|
||||
return panels.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
panel.url_path === "lovelace"
|
||||
? this.hass.localize("panel.states")
|
||||
panel.url_path === this.hass.defaultPanel
|
||||
? panel.title || 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
|
||||
panel.icon,
|
||||
panel.url_path === this.hass.defaultPanel && !panel.icon
|
||||
? mdiViewDashboard
|
||||
: undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private async _handlePanelTap(ev: Event) {
|
||||
const path = __DEMO__
|
||||
? (ev.currentTarget as HTMLAnchorElement).getAttribute("href")!
|
||||
: (ev.currentTarget as HTMLAnchorElement).href;
|
||||
navigate(this, path);
|
||||
}
|
||||
|
||||
private _renderPanel(
|
||||
urlPath: string,
|
||||
title: string | null,
|
||||
@@ -679,7 +681,6 @@ class HaSidebar extends LitElement {
|
||||
href=${`/${urlPath}`}
|
||||
data-panel=${urlPath}
|
||||
tabindex="-1"
|
||||
@panel-tap=${this._handlePanelTap}
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
@@ -767,6 +768,9 @@ class HaSidebar extends LitElement {
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
:host([narrow]) .title {
|
||||
padding: 0 16px;
|
||||
}
|
||||
:host([expanded]) .title {
|
||||
display: initial;
|
||||
}
|
||||
@@ -989,8 +993,4 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-sidebar": HaSidebar;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"panel-tap": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -279,6 +279,7 @@ class LocationEditor extends LitElement {
|
||||
}
|
||||
#map {
|
||||
height: 100%;
|
||||
background: inherit;
|
||||
}
|
||||
.leaflet-edit-move {
|
||||
border-radius: 50%;
|
||||
|
@@ -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, mdiClose, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
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";
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
browseLocalMediaPlayer,
|
||||
browseMediaPlayer,
|
||||
BROWSER_SOURCE,
|
||||
MediaClassBrowserSettings,
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
} from "../../data/media-player";
|
||||
@@ -93,34 +93,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
this._navigate(item);
|
||||
}
|
||||
|
||||
private _renderError(err: { message: string; code: string }) {
|
||||
if (err.message === "Media directory does not exist.") {
|
||||
return html`
|
||||
<h2>No local media found.</h2>
|
||||
<p>
|
||||
It looks like you have not yet created a media directory.
|
||||
<br />Create a directory with the name <b>"media"</b> in the
|
||||
configuration directory of Home Assistant
|
||||
(${this.hass.config.config_dir}). <br />Place your video, audio and
|
||||
image files in this directory to be able to browse and play them in
|
||||
the browser or on supported media players.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Check the
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/media_source/#local-media"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>documentation</a
|
||||
>
|
||||
for more info
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return err.message;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._loading) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
@@ -136,7 +108,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
text: this._renderError(this._error),
|
||||
});
|
||||
} else {
|
||||
return html`<div class="container error">
|
||||
return html`<div class="container">
|
||||
${this._renderError(this._error)}
|
||||
</div>`;
|
||||
}
|
||||
@@ -155,17 +127,12 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
? this._mediaPlayerItems[this._mediaPlayerItems.length - 2]
|
||||
: undefined;
|
||||
|
||||
const hasExpandableChildren:
|
||||
| MediaPlayerItem
|
||||
| undefined = this._hasExpandableChildren(currentItem.children);
|
||||
|
||||
const showImages: boolean | undefined = currentItem.children?.some(
|
||||
(child) => child.thumbnail && child.thumbnail !== currentItem.thumbnail
|
||||
);
|
||||
|
||||
const mediaType = this.hass.localize(
|
||||
`ui.components.media-browser.content-type.${currentItem.media_content_type}`
|
||||
const subtitle = this.hass.localize(
|
||||
`ui.components.media-browser.class.${currentItem.media_class}`
|
||||
);
|
||||
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
|
||||
const childrenMediaClass =
|
||||
MediaClassBrowserSettings[currentItem.children_media_class];
|
||||
|
||||
return html`
|
||||
<div
|
||||
@@ -174,102 +141,112 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
"no-dialog": !this.dialog,
|
||||
})}"
|
||||
>
|
||||
<div class="header-content">
|
||||
${currentItem.thumbnail
|
||||
? html`
|
||||
<div
|
||||
class="img"
|
||||
style=${styleMap({
|
||||
backgroundImage: currentItem.thumbnail
|
||||
? `url(${currentItem.thumbnail})`
|
||||
: "none",
|
||||
})}
|
||||
>
|
||||
${this._narrow && currentItem?.can_play
|
||||
? html`
|
||||
<mwc-fab
|
||||
mini
|
||||
.item=${currentItem}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
<div class="header-wrapper">
|
||||
<div class="header-content">
|
||||
${currentItem.thumbnail
|
||||
? html`
|
||||
<div
|
||||
class="img"
|
||||
style=${styleMap({
|
||||
backgroundImage: currentItem.thumbnail
|
||||
? `url(${currentItem.thumbnail})`
|
||||
: "none",
|
||||
})}
|
||||
>
|
||||
${this._narrow && currentItem?.can_play
|
||||
? html`
|
||||
<mwc-fab
|
||||
mini
|
||||
.item=${currentItem}
|
||||
@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}`
|
||||
)}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}`
|
||||
)}
|
||||
</mwc-fab>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
<div class="header-info">
|
||||
<div class="breadcrumb">
|
||||
${previousItem
|
||||
</mwc-fab>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
<div class="header-info">
|
||||
<div class="breadcrumb">
|
||||
${previousItem
|
||||
? html`
|
||||
<div class="previous-title" @click=${this.navigateBack}>
|
||||
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
|
||||
${previousItem.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<h1 class="title">${currentItem.title}</h1>
|
||||
${subtitle
|
||||
? html`
|
||||
<h2 class="subtitle">
|
||||
${subtitle}
|
||||
</h2>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${currentItem.can_play &&
|
||||
(!currentItem.thumbnail || !this._narrow)
|
||||
? html`
|
||||
<div class="previous-title" @click=${this.navigateBack}>
|
||||
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
|
||||
${previousItem.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<h1 class="title">${currentItem.title}</h1>
|
||||
${mediaType
|
||||
? html`
|
||||
<h2 class="subtitle">
|
||||
${mediaType}
|
||||
</h2>
|
||||
<mwc-button
|
||||
raised
|
||||
.item=${currentItem}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-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>
|
||||
${currentItem.can_play && (!currentItem.thumbnail || !this._narrow)
|
||||
? html`
|
||||
<mwc-button
|
||||
raised
|
||||
.item=${currentItem}
|
||||
@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>
|
||||
${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>
|
||||
${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>
|
||||
${this._error
|
||||
? html`<div class="container error">
|
||||
${this._renderError(this._error)}
|
||||
</div>`
|
||||
? html`
|
||||
<div class="container error">
|
||||
${this._renderError(this._error)}
|
||||
</div>
|
||||
`
|
||||
: currentItem.children?.length
|
||||
? hasExpandableChildren
|
||||
? childrenMediaClass.layout === "grid"
|
||||
? html`
|
||||
<div class="children">
|
||||
<div
|
||||
class="children ${classMap({
|
||||
portrait: childrenMediaClass.thumbnail_ratio === "portrait",
|
||||
})}"
|
||||
>
|
||||
${currentItem.children.map(
|
||||
(child) => html`
|
||||
<div
|
||||
@@ -286,11 +263,16 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
: "none",
|
||||
})}
|
||||
>
|
||||
${child.can_expand && !child.thumbnail
|
||||
${!child.thumbnail
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class="folder"
|
||||
.path=${mdiFolder}
|
||||
.path=${MediaClassBrowserSettings[
|
||||
child.media_class === "directory"
|
||||
? child.children_media_class ||
|
||||
child.media_class
|
||||
: child.media_class
|
||||
].icon}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
@@ -298,7 +280,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${child.can_play
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
class="play"
|
||||
class="play ${classMap({
|
||||
can_expand: child.can_expand,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
@@ -330,7 +314,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${currentItem.children.map(
|
||||
(child) => html`
|
||||
<mwc-list-item
|
||||
@click=${this._actionClicked}
|
||||
@click=${this._childClicked}
|
||||
.item=${child}
|
||||
graphic="avatar"
|
||||
hasMeta
|
||||
@@ -339,7 +323,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
<div
|
||||
class="graphic"
|
||||
style=${ifDefined(
|
||||
showImages && child.thumbnail
|
||||
mediaClass.show_list_images && child.thumbnail
|
||||
? `background-image: url(${child.thumbnail})`
|
||||
: undefined
|
||||
)}
|
||||
@@ -347,7 +331,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
>
|
||||
<mwc-icon-button
|
||||
class="play ${classMap({
|
||||
show: !showImages || !child.thumbnail,
|
||||
show:
|
||||
!mediaClass.show_list_images || !child.thumbnail,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
@@ -367,9 +352,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: html`<div class="container">
|
||||
${this.hass.localize("ui.components.media-browser.no_items")}
|
||||
</div>`}
|
||||
: html`
|
||||
<div class="container">
|
||||
${this.hass.localize("ui.components.media-browser.no_items")}
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -504,14 +491,38 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
this._resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
private _hasExpandableChildren = memoizeOne((children?: MediaPlayerItem[]) =>
|
||||
children?.find((item: MediaPlayerItem) => item.can_expand)
|
||||
);
|
||||
|
||||
private _closeDialogAction(): void {
|
||||
fireEvent(this, "close-dialog");
|
||||
}
|
||||
|
||||
private _renderError(err: { message: string; code: string }) {
|
||||
if (err.message === "Media directory does not exist.") {
|
||||
return html`
|
||||
<h2>No local media found.</h2>
|
||||
<p>
|
||||
It looks like you have not yet created a media directory.
|
||||
<br />Create a directory with the name <b>"media"</b> in the
|
||||
configuration directory of Home Assistant
|
||||
(${this.hass.config.config_dir}). <br />Place your video, audio and
|
||||
image files in this directory to be able to browse and play them in
|
||||
the browser or on supported media players.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Check the
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/media_source/#local-media"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>documentation</a
|
||||
>
|
||||
for more info
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return html`<span class="error">err.message</span>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -529,12 +540,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
display: block;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--card-background-color);
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
@@ -543,6 +551,10 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
padding: 20px 24px 10px;
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -570,6 +582,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
.header-info mwc-button {
|
||||
display: block;
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
@@ -630,13 +643,20 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(var(--media-browse-item-size, 175px), 0.33fr)
|
||||
minmax(var(--media-browse-item-size, 175px), 0.1fr)
|
||||
);
|
||||
grid-gap: 16px;
|
||||
margin: 8px 0px;
|
||||
padding: 0px 24px;
|
||||
}
|
||||
|
||||
:host([dialog]) .children {
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(var(--media-browse-item-size, 175px), 0.33fr)
|
||||
);
|
||||
}
|
||||
|
||||
.child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -648,7 +668,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
.children ha-card {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
@@ -656,6 +676,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transition: padding-bottom 0.1s ease-out;
|
||||
}
|
||||
|
||||
.portrait.children ha-card {
|
||||
padding-bottom: 150%;
|
||||
}
|
||||
|
||||
.child .folder,
|
||||
@@ -671,24 +696,43 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
.child .play {
|
||||
transition: color 0.5s;
|
||||
border-radius: 50%;
|
||||
bottom: calc(50% - 35px);
|
||||
right: calc(50% - 35px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
|
||||
.child .play:not(.can_expand) {
|
||||
--mdc-icon-button-size: 70px;
|
||||
--mdc-icon-size: 48px;
|
||||
}
|
||||
|
||||
.ha-card-parent:hover .play:not(.can_expand) {
|
||||
opacity: 1;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.child .play.can_expand {
|
||||
opacity: 1;
|
||||
background-color: rgba(var(--rgb-card-background-color), 0.5);
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
transition: all 0.5s;
|
||||
background-color: rgba(var(--rgb-card-background-color), 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.child .play:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-card:hover {
|
||||
.ha-card-parent:hover ha-card {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.child .title {
|
||||
font-size: 16px;
|
||||
padding-top: 8px;
|
||||
padding-left: 2px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
@@ -698,6 +742,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
.child .type {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
mwc-list-item .graphic {
|
||||
|
@@ -13,3 +13,5 @@ export const extractApiErrorMessage = (error: any): string => {
|
||||
: error.body || "Unknown error, see logs"
|
||||
: error;
|
||||
};
|
||||
|
||||
export const ignoredStatusCodes = new Set([502, 503, 504]);
|
||||
|
@@ -1,5 +1,23 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import {
|
||||
mdiFolder,
|
||||
mdiPlaylistMusic,
|
||||
mdiFileMusic,
|
||||
mdiAlbum,
|
||||
mdiMusic,
|
||||
mdiTelevisionClassic,
|
||||
mdiMovie,
|
||||
mdiVideo,
|
||||
mdiImage,
|
||||
mdiWeb,
|
||||
mdiGamepadVariant,
|
||||
mdiAccountMusic,
|
||||
mdiPodcast,
|
||||
mdiApplication,
|
||||
mdiAccountMusicOutline,
|
||||
mdiDramaMasks,
|
||||
} from "@mdi/js";
|
||||
|
||||
export const SUPPORT_PAUSE = 1;
|
||||
export const SUPPORT_SEEK = 2;
|
||||
@@ -22,6 +40,66 @@ export type MediaPlayerBrowseAction = "pick" | "play";
|
||||
|
||||
export const BROWSER_SOURCE = "browser";
|
||||
|
||||
export type MediaClassBrowserSetting = {
|
||||
icon: string;
|
||||
thumbnail_ratio?: string;
|
||||
layout?: string;
|
||||
show_list_images?: boolean;
|
||||
};
|
||||
|
||||
export const MediaClassBrowserSettings: {
|
||||
[type: string]: MediaClassBrowserSetting;
|
||||
} = {
|
||||
album: { icon: mdiAlbum, layout: "grid" },
|
||||
app: { icon: mdiApplication, layout: "grid" },
|
||||
artist: { icon: mdiAccountMusic, layout: "grid", show_list_images: true },
|
||||
channel: {
|
||||
icon: mdiTelevisionClassic,
|
||||
thumbnail_ratio: "portrait",
|
||||
layout: "grid",
|
||||
},
|
||||
composer: {
|
||||
icon: mdiAccountMusicOutline,
|
||||
layout: "grid",
|
||||
show_list_images: true,
|
||||
},
|
||||
contributing_artist: {
|
||||
icon: mdiAccountMusic,
|
||||
layout: "grid",
|
||||
show_list_images: true,
|
||||
},
|
||||
directory: { icon: mdiFolder, layout: "grid", show_list_images: true },
|
||||
episode: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
game: {
|
||||
icon: mdiGamepadVariant,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
genre: { icon: mdiDramaMasks, layout: "grid", show_list_images: true },
|
||||
image: { icon: mdiImage, layout: "grid" },
|
||||
movie: { icon: mdiMovie, thumbnail_ratio: "portrait", layout: "grid" },
|
||||
music: { icon: mdiMusic },
|
||||
playlist: { icon: mdiPlaylistMusic, layout: "grid", show_list_images: true },
|
||||
podcast: { icon: mdiPodcast, layout: "grid" },
|
||||
season: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
track: { icon: mdiFileMusic },
|
||||
tv_show: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
url: { icon: mdiWeb },
|
||||
video: { icon: mdiVideo, layout: "grid" },
|
||||
};
|
||||
|
||||
export interface MediaPickedEvent {
|
||||
item: MediaPlayerItem;
|
||||
}
|
||||
@@ -40,6 +118,8 @@ export interface MediaPlayerItem {
|
||||
title: string;
|
||||
media_content_type: string;
|
||||
media_content_id: string;
|
||||
media_class: string;
|
||||
children_media_class: string;
|
||||
can_play: boolean;
|
||||
can_expand: boolean;
|
||||
thumbnail?: string;
|
||||
|
17
src/data/refresh_token.ts
Normal file
17
src/data/refresh_token.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"hass-refresh-tokens": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export interface RefreshToken {
|
||||
client_icon?: string;
|
||||
client_id: string;
|
||||
client_name?: string;
|
||||
created_at: string;
|
||||
id: string;
|
||||
is_current: boolean;
|
||||
last_used_at?: string;
|
||||
last_used_ip?: string;
|
||||
type: "normal" | "long_lived_access_token";
|
||||
}
|
@@ -200,7 +200,7 @@ export const weatherSVGStyles = css`
|
||||
fill: var(--weather-icon-sun-color, #fdd93c);
|
||||
}
|
||||
.moon {
|
||||
fill: var(--weather-icon-moon-color, #fdf9cc);
|
||||
fill: var(--weather-icon-moon-color, #fcf497);
|
||||
}
|
||||
.cloud-back {
|
||||
fill: var(--weather-icon-cloud-back-color, #d4d4d4);
|
||||
|
@@ -1,20 +1,27 @@
|
||||
import { Connection, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
|
||||
interface RenderTemplateResult {
|
||||
export interface RenderTemplateResult {
|
||||
result: string;
|
||||
listeners: TemplateListeners;
|
||||
}
|
||||
|
||||
interface TemplateListeners {
|
||||
all: boolean;
|
||||
domains: string[];
|
||||
entities: string[];
|
||||
}
|
||||
|
||||
export const subscribeRenderTemplate = (
|
||||
conn: Connection,
|
||||
onChange: (result: string) => void,
|
||||
onChange: (result: RenderTemplateResult) => void,
|
||||
params: {
|
||||
template: string;
|
||||
entity_ids?: string | string[];
|
||||
variables?: object;
|
||||
}
|
||||
): Promise<UnsubscribeFunc> => {
|
||||
return conn.subscribeMessage(
|
||||
(msg: RenderTemplateResult) => onChange(msg.result),
|
||||
{ type: "render_template", ...params }
|
||||
);
|
||||
return conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), {
|
||||
type: "render_template",
|
||||
...params,
|
||||
});
|
||||
};
|
||||
|
@@ -57,7 +57,8 @@ class DialogBox extends LitElement {
|
||||
open
|
||||
?scrimClickAction=${this._params.prompt}
|
||||
?escapeKeyAction=${this._params.prompt}
|
||||
@closed=${this._dismiss}
|
||||
@closed=${this._dialogClosed}
|
||||
defaultAction="ignore"
|
||||
.heading=${this._params.title
|
||||
? this._params.title
|
||||
: this._params.confirmation &&
|
||||
@@ -78,10 +79,10 @@ class DialogBox extends LitElement {
|
||||
${this._params.prompt
|
||||
? html`
|
||||
<paper-input
|
||||
autofocus
|
||||
dialogInitialFocus
|
||||
.value=${this._value}
|
||||
@value-changed=${this._valueChanged}
|
||||
@keyup=${this._handleKeyUp}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._params.inputLabel
|
||||
? this._params.inputLabel
|
||||
: ""}
|
||||
@@ -100,7 +101,11 @@ class DialogBox extends LitElement {
|
||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-button @click=${this._confirm} slot="primaryAction">
|
||||
<mwc-button
|
||||
@click=${this._confirm}
|
||||
?dialogInitialFocus=${!this._params.prompt}
|
||||
slot="primaryAction"
|
||||
>
|
||||
${this._params.confirmText
|
||||
? this._params.confirmText
|
||||
: this.hass.localize("ui.dialogs.generic.ok")}
|
||||
@@ -133,7 +138,17 @@ class DialogBox extends LitElement {
|
||||
this._close();
|
||||
}
|
||||
|
||||
private _dialogClosed(ev) {
|
||||
if (ev.detail.action === "ignore") {
|
||||
return;
|
||||
}
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
@@ -12,12 +12,13 @@ import {
|
||||
import "../../../components/ha-relative-time";
|
||||
import { triggerAutomation } from "../../../data/automation";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
|
||||
@customElement("more-info-automation")
|
||||
class MoreInfoAutomation extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: HassEntity;
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
@@ -36,7 +37,7 @@ class MoreInfoAutomation extends LitElement {
|
||||
<div class="actions">
|
||||
<mwc-button
|
||||
@click=${this.handleAction}
|
||||
.disabled=${this.stateObj!.state === "unavailable"}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj!.state)}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</mwc-button>
|
||||
|
@@ -48,7 +48,7 @@ class MoreInfoCamera extends LitElement {
|
||||
<ha-camera-stream
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
showcontrols
|
||||
controls
|
||||
></ha-camera-stream>
|
||||
${this._cameraPrefs
|
||||
? html`
|
||||
|
@@ -61,20 +61,20 @@ class MoreInfoLight extends LitElement {
|
||||
"is-on": this.stateObj.state === "on",
|
||||
})}"
|
||||
>
|
||||
${supportsFeature(this.stateObj!, SUPPORT_BRIGHTNESS)
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
caption=${this.hass.localize("ui.card.light.brightness")}
|
||||
icon="hass:brightness-5"
|
||||
min="1"
|
||||
max="255"
|
||||
value=${this._brightnessSliderValue}
|
||||
@change=${this._brightnessSliderChanged}
|
||||
></ha-labeled-slider>
|
||||
`
|
||||
: ""}
|
||||
${this.stateObj.state === "on"
|
||||
? html`
|
||||
${supportsFeature(this.stateObj!, SUPPORT_BRIGHTNESS)
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
caption=${this.hass.localize("ui.card.light.brightness")}
|
||||
icon="hass:brightness-5"
|
||||
min="1"
|
||||
max="255"
|
||||
value=${this._brightnessSliderValue}
|
||||
@change=${this._brightnessSliderChanged}
|
||||
></ha-labeled-slider>
|
||||
`
|
||||
: ""}
|
||||
${supportsFeature(this.stateObj, SUPPORT_COLOR_TEMP)
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
|
@@ -188,14 +188,17 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
<div class="tts">
|
||||
<paper-input
|
||||
id="ttsInput"
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
.label=${this.hass.localize(
|
||||
"ui.card.media_player.text_to_speak"
|
||||
)}
|
||||
@keydown=${this._ttsCheckForEnter}
|
||||
></paper-input>
|
||||
<ha-icon-button icon="hass:send" @click=${
|
||||
this._sendTTS
|
||||
}></ha-icon-button>
|
||||
<ha-icon-button
|
||||
icon="hass:send"
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
@click=${this._sendTTS}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
@@ -13,10 +13,15 @@ import {
|
||||
} from "lit-element";
|
||||
import { cache } from "lit-html/directives/cache";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const";
|
||||
import {
|
||||
DOMAINS_MORE_INFO_NO_HISTORY,
|
||||
DOMAINS_WITH_MORE_INFO,
|
||||
} from "../../common/const";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { stateMoreInfoType } from "../../common/entity/state_more_info_type";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-header-bar";
|
||||
@@ -30,21 +35,38 @@ import "../../state-summary/state-card-content";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import "./ha-more-info-history";
|
||||
import "./more-info-content";
|
||||
import "./ha-more-info-logbook";
|
||||
|
||||
const DOMAINS_NO_INFO = ["camera", "configurator"];
|
||||
const CONTROL_DOMAINS = [
|
||||
"light",
|
||||
"media_player",
|
||||
"vacuum",
|
||||
"alarm_control_panel",
|
||||
"climate",
|
||||
"humidifier",
|
||||
"weather",
|
||||
];
|
||||
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
|
||||
const EDITABLE_DOMAINS = ["script"];
|
||||
|
||||
const MORE_INFO_CONTROL_IMPORT = {
|
||||
alarm_control_panel: () => import("./controls/more-info-alarm_control_panel"),
|
||||
automation: () => import("./controls/more-info-automation"),
|
||||
camera: () => import("./controls/more-info-camera"),
|
||||
climate: () => import("./controls/more-info-climate"),
|
||||
configurator: () => import("./controls/more-info-configurator"),
|
||||
counter: () => import("./controls/more-info-counter"),
|
||||
cover: () => import("./controls/more-info-cover"),
|
||||
fan: () => import("./controls/more-info-fan"),
|
||||
group: () => import("./controls/more-info-group"),
|
||||
humidifier: () => import("./controls/more-info-humidifier"),
|
||||
input_datetime: () => import("./controls/more-info-input_datetime"),
|
||||
light: () => import("./controls/more-info-light"),
|
||||
lock: () => import("./controls/more-info-lock"),
|
||||
media_player: () => import("./controls/more-info-media_player"),
|
||||
person: () => import("./controls/more-info-person"),
|
||||
script: () => import("./controls/more-info-script"),
|
||||
sun: () => import("./controls/more-info-sun"),
|
||||
timer: () => import("./controls/more-info-timer"),
|
||||
vacuum: () => import("./controls/more-info-vacuum"),
|
||||
water_heater: () => import("./controls/more-info-water_heater"),
|
||||
weather: () => import("./controls/more-info-weather"),
|
||||
hidden: () => {},
|
||||
default: () => import("./controls/more-info-default"),
|
||||
};
|
||||
|
||||
export interface MoreInfoDialogParams {
|
||||
entityId: string | null;
|
||||
}
|
||||
@@ -57,6 +79,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
@internalProperty() private _entityId?: string | null;
|
||||
|
||||
@internalProperty() private _moreInfoType?: string;
|
||||
|
||||
@internalProperty() private _currTabIndex = 0;
|
||||
|
||||
public showDialog(params: MoreInfoDialogParams) {
|
||||
@@ -73,6 +97,23 @@ export class MoreInfoDialog extends LitElement {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (!this.hass || !this._entityId || !changedProperties.has("_entityId")) {
|
||||
return;
|
||||
}
|
||||
const stateObj = this.hass.states[this._entityId];
|
||||
if (!stateObj) {
|
||||
return;
|
||||
}
|
||||
if (stateObj.attributes && "custom_ui_more_info" in stateObj.attributes) {
|
||||
this._moreInfoType = stateObj.attributes.custom_ui_more_info;
|
||||
} else {
|
||||
const type = stateMoreInfoType(stateObj);
|
||||
this._moreInfoType = `more-info-${type}`;
|
||||
MORE_INFO_CONTROL_IMPORT[type]();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._entityId) {
|
||||
return html``;
|
||||
@@ -137,7 +178,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</ha-header-bar>
|
||||
${CONTROL_DOMAINS.includes(domain) &&
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) &&
|
||||
this._computeShowHistoryComponent(entityId)
|
||||
? html`
|
||||
<mwc-tab-bar
|
||||
@@ -171,17 +212,23 @@ export class MoreInfoDialog extends LitElement {
|
||||
.hass=${this.hass}
|
||||
></state-card-content>
|
||||
`}
|
||||
<more-info-content
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></more-info-content>
|
||||
${CONTROL_DOMAINS.includes(domain) ||
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
|
||||
!this._computeShowHistoryComponent(entityId)
|
||||
? ""
|
||||
: html`<ha-more-info-history
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-history>`}
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-history>
|
||||
<ha-more-info-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-logbook>`}
|
||||
${this._moreInfoType
|
||||
? dynamicElement(this._moreInfoType, {
|
||||
hass: this.hass,
|
||||
stateObj,
|
||||
})
|
||||
: ""}
|
||||
${stateObj.attributes.restored
|
||||
? html`
|
||||
<p>
|
||||
@@ -210,6 +257,10 @@ export class MoreInfoDialog extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-history>
|
||||
<ha-more-info-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-logbook>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
@@ -223,7 +274,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
private _computeShowHistoryComponent(entityId) {
|
||||
return (
|
||||
isComponentLoaded(this.hass, "history") &&
|
||||
(isComponentLoaded(this.hass, "history") ||
|
||||
isComponentLoaded(this.hass, "logbook")) &&
|
||||
!DOMAINS_MORE_INFO_NO_HISTORY.includes(computeDomain(entityId))
|
||||
);
|
||||
}
|
||||
|
@@ -8,14 +8,11 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import "../../components/ha-circular-progress";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/state-history-charts";
|
||||
import { getRecentWithCache } from "../../data/cached-history";
|
||||
import { HistoryResult } from "../../data/history";
|
||||
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
||||
import "../../panels/logbook/ha-logbook";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@@ -27,79 +24,58 @@ export class MoreInfoHistory extends LitElement {
|
||||
|
||||
@internalProperty() private _stateHistory?: HistoryResult;
|
||||
|
||||
@internalProperty() private _entries?: LogbookEntry[];
|
||||
|
||||
@internalProperty() private _persons = {};
|
||||
|
||||
private _historyRefreshInterval?: number;
|
||||
private _throttleGetStateHistory = throttle(() => {
|
||||
this._getStateHistory();
|
||||
}, 10000);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entityId) {
|
||||
return html``;
|
||||
}
|
||||
const stateObj = this.hass.states[this.entityId];
|
||||
|
||||
if (!stateObj) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`<state-history-charts
|
||||
up-to-now
|
||||
.hass=${this.hass}
|
||||
.historyData=${this._stateHistory}
|
||||
.isLoadingData=${!this._stateHistory}
|
||||
></state-history-charts>
|
||||
${!this._entries
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: this._entries.length
|
||||
? html`
|
||||
<ha-logbook
|
||||
narrow
|
||||
no-icon
|
||||
no-name
|
||||
style=${styleMap({
|
||||
height: `${(this._entries.length + 1) * 56}px`,
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
.entries=${this._entries}
|
||||
.userIdToName=${this._persons}
|
||||
></ha-logbook>
|
||||
`
|
||||
: html`<div class="no-entries">
|
||||
${this.hass.localize("ui.components.logbook.entries_not_found")}
|
||||
</div>`}`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._fetchPersonNames();
|
||||
return html`${isComponentLoaded(this.hass, "history")
|
||||
? html`<state-history-charts
|
||||
up-to-now
|
||||
.hass=${this.hass}
|
||||
.historyData=${this._stateHistory}
|
||||
.isLoadingData=${!this._stateHistory}
|
||||
></state-history-charts>`
|
||||
: ""} `;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this.entityId) {
|
||||
clearInterval(this._historyRefreshInterval);
|
||||
}
|
||||
|
||||
if (changedProps.has("entityId")) {
|
||||
this._stateHistory = undefined;
|
||||
this._entries = undefined;
|
||||
|
||||
this._getStateHistory();
|
||||
this._getLogBookData();
|
||||
if (!this.entityId) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(this._historyRefreshInterval);
|
||||
this._historyRefreshInterval = window.setInterval(() => {
|
||||
this._getStateHistory();
|
||||
}, 60 * 1000);
|
||||
this._throttleGetStateHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.entityId || !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
if (
|
||||
oldHass &&
|
||||
this.hass.states[this.entityId] !== oldHass?.states[this.entityId]
|
||||
) {
|
||||
// wait for commit of data (we only account for the default setting of 1 sec)
|
||||
setTimeout(this._throttleGetStateHistory, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private async _getStateHistory(): Promise<void> {
|
||||
if (!isComponentLoaded(this.hass, "history")) {
|
||||
return;
|
||||
}
|
||||
this._stateHistory = await getRecentWithCache(
|
||||
this.hass!,
|
||||
this.entityId,
|
||||
@@ -113,30 +89,6 @@ export class MoreInfoHistory extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private async _getLogBookData() {
|
||||
const yesterday = new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
|
||||
const now = new Date();
|
||||
this._entries = await getLogbookData(
|
||||
this.hass,
|
||||
yesterday.toISOString(),
|
||||
now.toISOString(),
|
||||
this.entityId,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private _fetchPersonNames() {
|
||||
Object.values(this.hass.states).forEach((entity) => {
|
||||
if (
|
||||
entity.attributes.user_id &&
|
||||
computeStateDomain(entity) === "person"
|
||||
) {
|
||||
this._persons[entity.attributes.user_id] =
|
||||
entity.attributes.friendly_name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -145,18 +97,6 @@ export class MoreInfoHistory extends LitElement {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
}
|
||||
ha-logbook {
|
||||
max-height: 360px;
|
||||
}
|
||||
|
||||
ha-circular-progress {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
171
src/dialogs/more-info/ha-more-info-logbook.ts
Normal file
171
src/dialogs/more-info/ha-more-info-logbook.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/state-history-charts";
|
||||
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
||||
import "../../panels/logbook/ha-logbook";
|
||||
import { haStyle, haStyleScrollbar } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-more-info-logbook")
|
||||
export class MoreInfoLogbook extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public entityId!: string;
|
||||
|
||||
@internalProperty() private _logbookEntries?: LogbookEntry[];
|
||||
|
||||
@internalProperty() private _persons = {};
|
||||
|
||||
private _lastLogbookDate?: Date;
|
||||
|
||||
private _throttleGetLogbookEntries = throttle(() => {
|
||||
this._getLogBookData();
|
||||
}, 10000);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entityId) {
|
||||
return html``;
|
||||
}
|
||||
const stateObj = this.hass.states[this.entityId];
|
||||
|
||||
if (!stateObj) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${isComponentLoaded(this.hass, "logbook")
|
||||
? !this._logbookEntries
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: this._logbookEntries.length
|
||||
? html`
|
||||
<ha-logbook
|
||||
class="ha-scrollbar"
|
||||
narrow
|
||||
no-icon
|
||||
no-name
|
||||
.hass=${this.hass}
|
||||
.entries=${this._logbookEntries}
|
||||
.userIdToName=${this._persons}
|
||||
></ha-logbook>
|
||||
`
|
||||
: html`<div class="no-entries">
|
||||
${this.hass.localize("ui.components.logbook.entries_not_found")}
|
||||
</div>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._fetchPersonNames();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("entityId")) {
|
||||
this._lastLogbookDate = undefined;
|
||||
this._logbookEntries = undefined;
|
||||
|
||||
if (!this.entityId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._throttleGetLogbookEntries();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.entityId || !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
if (
|
||||
oldHass &&
|
||||
this.hass.states[this.entityId] !== oldHass?.states[this.entityId]
|
||||
) {
|
||||
// wait for commit of data (we only account for the default setting of 1 sec)
|
||||
setTimeout(this._throttleGetLogbookEntries, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private async _getLogBookData() {
|
||||
if (!isComponentLoaded(this.hass, "logbook")) {
|
||||
return;
|
||||
}
|
||||
const lastDate =
|
||||
this._lastLogbookDate ||
|
||||
new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
|
||||
const now = new Date();
|
||||
const newEntries = await getLogbookData(
|
||||
this.hass,
|
||||
lastDate.toISOString(),
|
||||
now.toISOString(),
|
||||
this.entityId,
|
||||
true
|
||||
);
|
||||
this._logbookEntries = this._logbookEntries
|
||||
? [...newEntries, ...this._logbookEntries]
|
||||
: newEntries;
|
||||
this._lastLogbookDate = now;
|
||||
}
|
||||
|
||||
private _fetchPersonNames() {
|
||||
Object.values(this.hass.states).forEach((entity) => {
|
||||
if (
|
||||
entity.attributes.user_id &&
|
||||
computeStateDomain(entity) === "person"
|
||||
) {
|
||||
this._persons[entity.attributes.user_id] =
|
||||
entity.attributes.friendly_name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-logbook {
|
||||
max-height: 250px;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-logbook": MoreInfoLogbook;
|
||||
}
|
||||
}
|
@@ -1,73 +0,0 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { property, PropertyValues, UpdatingElement } from "lit-element";
|
||||
import dynamicContentUpdater from "../../common/dom/dynamic_content_updater";
|
||||
import { stateMoreInfoType } from "../../common/entity/state_more_info_type";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./controls/more-info-alarm_control_panel";
|
||||
import "./controls/more-info-automation";
|
||||
import "./controls/more-info-camera";
|
||||
import "./controls/more-info-climate";
|
||||
import "./controls/more-info-configurator";
|
||||
import "./controls/more-info-counter";
|
||||
import "./controls/more-info-cover";
|
||||
import "./controls/more-info-default";
|
||||
import "./controls/more-info-fan";
|
||||
import "./controls/more-info-group";
|
||||
import "./controls/more-info-humidifier";
|
||||
import "./controls/more-info-input_datetime";
|
||||
import "./controls/more-info-light";
|
||||
import "./controls/more-info-lock";
|
||||
import "./controls/more-info-media_player";
|
||||
import "./controls/more-info-person";
|
||||
import "./controls/more-info-script";
|
||||
import "./controls/more-info-sun";
|
||||
import "./controls/more-info-timer";
|
||||
import "./controls/more-info-vacuum";
|
||||
import "./controls/more-info-water_heater";
|
||||
import "./controls/more-info-weather";
|
||||
|
||||
class MoreInfoContent extends UpdatingElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
private _detachedChild?: ChildNode;
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.style.position = "relative";
|
||||
this.style.display = "block";
|
||||
}
|
||||
|
||||
// This is not a lit element, but an updating element, so we implement update
|
||||
protected update(changedProps: PropertyValues): void {
|
||||
super.update(changedProps);
|
||||
const stateObj = this.stateObj;
|
||||
const hass = this.hass;
|
||||
|
||||
if (!stateObj || !hass) {
|
||||
if (this.lastChild) {
|
||||
this._detachedChild = this.lastChild;
|
||||
// Detach child to prevent it from doing work.
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._detachedChild) {
|
||||
this.appendChild(this._detachedChild);
|
||||
this._detachedChild = undefined;
|
||||
}
|
||||
|
||||
const moreInfoType =
|
||||
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
|
||||
? stateObj.attributes.custom_ui_more_info
|
||||
: "more-info-" + stateMoreInfoType(stateObj);
|
||||
|
||||
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
|
||||
hass,
|
||||
stateObj,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-content", MoreInfoContent);
|
@@ -11,7 +11,12 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #111111;
|
||||
color: var(--primary-text-color, #e1e1e1);
|
||||
color: #e1e1e1;
|
||||
}
|
||||
ha-onboarding {
|
||||
--primary-text-color: #e1e1e1;
|
||||
--secondary-text-color: #9b9b9b;
|
||||
--disabled-text-color: #6f6f6f;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
|
@@ -3,26 +3,26 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
eventOptions,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
eventOptions,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { navigate } from "../common/navigate";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import { HomeAssistant, Route } from "../types";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-tab";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-tab";
|
||||
import { HomeAssistant, Route } from "../types";
|
||||
|
||||
export interface PageNavigation {
|
||||
path: string;
|
||||
@@ -132,7 +132,7 @@ class HassTabsSubpage extends LitElement {
|
||||
this.hass.language,
|
||||
this.narrow
|
||||
);
|
||||
|
||||
const showTabs = tabs.length > 1 || !this.narrow;
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
${this.mainPage
|
||||
@@ -152,7 +152,7 @@ class HassTabsSubpage extends LitElement {
|
||||
${this.narrow
|
||||
? html` <div class="main-title"><slot name="header"></slot></div> `
|
||||
: ""}
|
||||
${tabs.length > 1 || !this.narrow
|
||||
${showTabs
|
||||
? html`
|
||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||
${tabs}
|
||||
@@ -163,10 +163,15 @@ class HassTabsSubpage extends LitElement {
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" @scroll=${this._saveScrollPos}>
|
||||
<div
|
||||
class="content ${classMap({ tabs: showTabs })}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div id="fab"><slot name="fab"></slot></div>
|
||||
<div id="fab" class="${classMap({ tabs: showTabs })}">
|
||||
<slot name="fab"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -274,12 +279,13 @@ class HassTabsSubpage extends LitElement {
|
||||
margin-left: env(safe-area-inset-left);
|
||||
margin-right: env(safe-area-inset-right);
|
||||
height: calc(100% - 65px);
|
||||
height: calc(100% - 65px - env(safe-area-inset-bottom));
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
:host([narrow]) .content {
|
||||
:host([narrow]) .content.tabs {
|
||||
height: calc(100% - 128px);
|
||||
height: calc(100% - 128px - env(safe-area-inset-bottom));
|
||||
}
|
||||
@@ -290,7 +296,7 @@ class HassTabsSubpage extends LitElement {
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
:host([narrow]) #fab {
|
||||
:host([narrow]) #fab.tabs {
|
||||
bottom: calc(84px + env(safe-area-inset-bottom));
|
||||
}
|
||||
#fab[is-wide] {
|
||||
|
@@ -24,6 +24,7 @@ const NON_SWIPABLE_PANELS = ["map"];
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"hass-open-menu": undefined;
|
||||
"hass-toggle-menu": undefined;
|
||||
"hass-show-notifications": undefined;
|
||||
}
|
||||
@@ -92,6 +93,17 @@ class HomeAssistantMain extends LitElement {
|
||||
protected firstUpdated() {
|
||||
import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar");
|
||||
|
||||
this.addEventListener("hass-open-menu", () => {
|
||||
if (this._sidebarNarrow) {
|
||||
this.drawer.open();
|
||||
} else {
|
||||
fireEvent(this, "hass-dock-sidebar", {
|
||||
dock: "docked",
|
||||
});
|
||||
setTimeout(() => this.appLayout.resetLayout());
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("hass-toggle-menu", () => {
|
||||
if (this._sidebarNarrow) {
|
||||
if (this.drawer.opened) {
|
||||
|
@@ -6,9 +6,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
@@ -25,6 +25,7 @@ import {
|
||||
showAutomationEditor,
|
||||
triggerAutomation,
|
||||
} from "../../../data/automation";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@@ -35,9 +36,9 @@ import { showThingtalkDialog } from "./show-dialog-thingtalk";
|
||||
class HaAutomationPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public isWide!: boolean;
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@@ -58,7 +59,7 @@ class HaAutomationPicker extends LitElement {
|
||||
toggle: {
|
||||
title: "",
|
||||
type: "icon",
|
||||
template: (_toggle, automation) =>
|
||||
template: (_toggle, automation: any) =>
|
||||
html`
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
@@ -95,7 +96,7 @@ class HaAutomationPicker extends LitElement {
|
||||
<mwc-button
|
||||
.automation=${automation}
|
||||
@click=${(ev) => this._execute(ev)}
|
||||
.disabled=${automation.state === "unavailable"}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(automation.state)}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</mwc-button>
|
||||
|
@@ -59,6 +59,7 @@ import {
|
||||
showEntityEditorDialog,
|
||||
} from "./show-dialog-entity-editor";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
export interface StateEntity extends EntityRegistryEntry {
|
||||
readonly?: boolean;
|
||||
@@ -281,7 +282,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
|
||||
for (const entry of entities) {
|
||||
const entity = this.hass.states[entry.entity_id];
|
||||
const unavailable = entity?.state === "unavailable";
|
||||
const unavailable = entity?.state === UNAVAILABLE;
|
||||
const restored = entity?.attributes.restored;
|
||||
|
||||
if (!showUnavailable && unavailable) {
|
||||
|
@@ -6,9 +6,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
@@ -48,12 +48,12 @@ class HaInputNumberForm extends LitElement {
|
||||
this._max = item.max ?? 100;
|
||||
this._min = item.min ?? 0;
|
||||
this._mode = item.mode || "slider";
|
||||
this._step = item.step || 1;
|
||||
this._step = item.step ?? 1;
|
||||
this._unit_of_measurement = item.unit_of_measurement;
|
||||
} else {
|
||||
this._item = {
|
||||
min: 0,
|
||||
max: 0,
|
||||
max: 100,
|
||||
};
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
@@ -176,8 +176,10 @@ class HaInputNumberForm extends LitElement {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
const target = ev.target as any;
|
||||
const configValue = target.configValue;
|
||||
const value =
|
||||
target.type === "number" ? Number(ev.detail.value) : ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
@@ -11,9 +11,9 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
@@ -117,7 +117,6 @@ class OZWNetworkNodes extends LitElement {
|
||||
.data=${this._nodes}
|
||||
id="node_id"
|
||||
@row-click=${this._handleRowClicked}
|
||||
back-path="/config/ozw/network/${this.ozwInstance}/dashboard"
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
|
@@ -1,14 +1,21 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiContentCopy } from "@mdi/js";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import type { PaperTooltipElement } from "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/dialog/ha-paper-dialog";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
domainToName,
|
||||
fetchIntegrationManifest,
|
||||
@@ -16,12 +23,11 @@ import {
|
||||
IntegrationManifest,
|
||||
} from "../../../data/integration";
|
||||
import { getLoggedErrorIntegration } from "../../../data/system_log";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
||||
import { formatSystemLogTime } from "./util";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
class DialogSystemLogDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -30,6 +36,8 @@ class DialogSystemLogDetail extends LitElement {
|
||||
|
||||
@internalProperty() private _manifest?: IntegrationManifest;
|
||||
|
||||
@query("paper-tooltip") private _toolTip?: PaperTooltipElement;
|
||||
|
||||
public async showDialog(params: SystemLogDetailDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._manifest = undefined;
|
||||
@@ -66,13 +74,25 @@ class DialogSystemLogDetail extends LitElement {
|
||||
opened
|
||||
@opened-changed="${this._openedChanged}"
|
||||
>
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.logs.details",
|
||||
"level",
|
||||
item.level
|
||||
)}
|
||||
</h2>
|
||||
<div class="heading">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.logs.details",
|
||||
"level",
|
||||
item.level
|
||||
)}
|
||||
</h2>
|
||||
<mwc-icon-button id="copy" @click=${this._copyLog}>
|
||||
<ha-svg-icon .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<paper-tooltip
|
||||
manual-mode
|
||||
for="copy"
|
||||
position="top"
|
||||
animation-delay="0"
|
||||
>${this.hass.localize("ui.common.copied")}</paper-tooltip
|
||||
>
|
||||
</div>
|
||||
<paper-dialog-scrollable>
|
||||
<p>
|
||||
Logger: ${item.name}<br />
|
||||
@@ -148,6 +168,25 @@ class DialogSystemLogDetail extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _copyLog(): void {
|
||||
const copyElement = this.shadowRoot?.querySelector(
|
||||
"paper-dialog-scrollable"
|
||||
) as HTMLElement;
|
||||
|
||||
const selection = window.getSelection()!;
|
||||
const range = document.createRange();
|
||||
|
||||
range.selectNodeContents(copyElement);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
document.execCommand("copy");
|
||||
window.getSelection()!.removeAllRanges();
|
||||
|
||||
this._toolTip!.show();
|
||||
setTimeout(() => this._toolTip?.hide(), 3000);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
@@ -164,6 +203,15 @@ class DialogSystemLogDetail extends LitElement {
|
||||
pre {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.heading {
|
||||
display: flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.heading ha-svg-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -18,12 +18,14 @@ import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-input";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
Action,
|
||||
deleteScript,
|
||||
getScriptEditorInitData,
|
||||
ScriptConfig,
|
||||
triggerScript,
|
||||
MODES,
|
||||
MODES_MAX,
|
||||
} from "../../../data/script";
|
||||
@@ -193,6 +195,22 @@ export class HaScriptEditor extends LitElement {
|
||||
</paper-input>`
|
||||
: html``}
|
||||
</div>
|
||||
${this.scriptEntityId
|
||||
? html`
|
||||
<div class="card-actions layout horizontal justified center">
|
||||
<span></span>
|
||||
<mwc-button
|
||||
@click=${this._runScript}
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.activate_script"
|
||||
)}"
|
||||
?disabled=${this._dirty}
|
||||
>
|
||||
${this.hass.localize("ui.card.script.execute")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ``}
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
@@ -301,6 +319,18 @@ export class HaScriptEditor extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _runScript(ev) {
|
||||
ev.stopPropagation();
|
||||
await triggerScript(this.hass, this.scriptEntityId);
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.notification_toast.triggered",
|
||||
"name",
|
||||
this._config!.alias
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private _modeChanged(ev: CustomEvent) {
|
||||
const mode = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||
?.mode;
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
@@ -11,7 +13,7 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { deleteUser, fetchUsers, updateUser, User } from "../../../data/user";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
@@ -19,8 +21,6 @@ import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showAddUserDialog } from "./show-dialog-add-user";
|
||||
import { showUserDetailDialog } from "./show-dialog-user-detail";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-users")
|
||||
export class HaConfigUsers extends LitElement {
|
||||
@@ -56,7 +56,7 @@ export class HaConfigUsers extends LitElement {
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "25%",
|
||||
width: "30%",
|
||||
template: (groupIds) => html`
|
||||
${this.hass.localize(`groups.${groupIds[0]}`)}
|
||||
`,
|
||||
@@ -66,6 +66,7 @@ export class HaConfigUsers extends LitElement {
|
||||
"ui.panel.config.users.picker.headers.system"
|
||||
),
|
||||
type: "icon",
|
||||
width: "80px",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
template: (generated) => html`
|
||||
|
@@ -13,7 +13,10 @@ import { classMap } from "lit-html/directives/class-map";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-code-editor";
|
||||
import { subscribeRenderTemplate } from "../../../data/ws-templates";
|
||||
import {
|
||||
RenderTemplateResult,
|
||||
subscribeRenderTemplate,
|
||||
} from "../../../data/ws-templates";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
@@ -31,10 +34,9 @@ The temperature is {{ my_test_json.temperature }} {{ my_test_json.unit }}.
|
||||
The sun will rise at {{ as_timestamp(strptime(state_attr("sun.sun", "next_rising"), "")) | timestamp_local }}.
|
||||
{%- endif %}
|
||||
|
||||
For loop example getting 3 entity values:
|
||||
For loop example getting entity values in the weather domain:
|
||||
|
||||
{% for states in states | slice(3) -%}
|
||||
{% set state = states | first %}
|
||||
{% for state in states.weather -%}
|
||||
{%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}
|
||||
{{ state.name | lower }} is {{state.state_with_unit}}
|
||||
{%- endfor %}.`;
|
||||
@@ -45,11 +47,11 @@ class HaPanelDevTemplate extends LitElement {
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@internalProperty() private _error = false;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _rendering = false;
|
||||
|
||||
@internalProperty() private _processed = "";
|
||||
@internalProperty() private _templateResult?: RenderTemplateResult;
|
||||
|
||||
@internalProperty() private _unsubRenderTemplate?: Promise<UnsubscribeFunc>;
|
||||
|
||||
@@ -140,9 +142,65 @@ class HaPanelDevTemplate extends LitElement {
|
||||
.active=${this._rendering}
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
<pre class="rendered ${classMap({ error: this._error })}">
|
||||
${this._processed}</pre
|
||||
>
|
||||
|
||||
<pre
|
||||
class="rendered ${classMap({ error: Boolean(this._error) })}"
|
||||
><!-- display: block -->${this._error}${this._templateResult
|
||||
?.result}</pre>
|
||||
${!this._templateResult?.listeners
|
||||
? ""
|
||||
: this._templateResult.listeners.all
|
||||
? html`
|
||||
<h3 class="all_listeners">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.all_listeners"
|
||||
)}
|
||||
</h3>
|
||||
`
|
||||
: this._templateResult.listeners.domains.length ||
|
||||
this._templateResult.listeners.entities.length
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.listeners"
|
||||
)}
|
||||
</h3>
|
||||
<ul>
|
||||
${this._templateResult.listeners.domains
|
||||
.sort()
|
||||
.map(
|
||||
(domain) =>
|
||||
html`
|
||||
<li>
|
||||
<b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.domain"
|
||||
)}</b
|
||||
>: ${domain}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
${this._templateResult.listeners.entities
|
||||
.sort()
|
||||
.map(
|
||||
(entity_id) =>
|
||||
html`
|
||||
<li>
|
||||
<b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.entity"
|
||||
)}</b
|
||||
>: ${entity_id}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
`
|
||||
: html` <span class="all_listeners">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.no_listeners"
|
||||
)}
|
||||
</span>`}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -190,6 +248,12 @@ ${this._processed}</pre
|
||||
@apply --paper-font-code1;
|
||||
clear: both;
|
||||
white-space: pre-wrap;
|
||||
background-color: var(--secondary-background-color);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.all_listeners {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.rendered.error {
|
||||
@@ -211,7 +275,7 @@ ${this._processed}</pre
|
||||
private _templateChanged(ev) {
|
||||
this._template = ev.detail.value;
|
||||
if (this._error) {
|
||||
this._error = false;
|
||||
this._error = undefined;
|
||||
}
|
||||
this._debounceRender();
|
||||
}
|
||||
@@ -223,7 +287,8 @@ ${this._processed}</pre
|
||||
this._unsubRenderTemplate = subscribeRenderTemplate(
|
||||
this.hass.connection,
|
||||
(result) => {
|
||||
this._processed = result;
|
||||
this._templateResult = result;
|
||||
this._error = undefined;
|
||||
},
|
||||
{
|
||||
template: this._template,
|
||||
@@ -231,9 +296,10 @@ ${this._processed}</pre
|
||||
);
|
||||
await this._unsubRenderTemplate;
|
||||
} catch (err) {
|
||||
this._error = true;
|
||||
this._error = "Unknown error";
|
||||
if (err.message) {
|
||||
this._processed = err.message;
|
||||
this._error = err.message;
|
||||
this._templateResult = undefined;
|
||||
}
|
||||
this._unsubRenderTemplate = undefined;
|
||||
} finally {
|
||||
|
@@ -79,10 +79,12 @@ class HaPanelHistory extends LitElement {
|
||||
></ha-date-range-picker>
|
||||
</div>
|
||||
${this._isLoading
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>`
|
||||
? html`<div class="progress-wrapper">
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: html`
|
||||
<state-history-charts
|
||||
.hass=${this.hass}
|
||||
@@ -196,6 +198,19 @@ class HaPanelHistory extends LitElement {
|
||||
.content {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.progress-wrapper {
|
||||
height: calc(100vh - 136px);
|
||||
}
|
||||
|
||||
:host([narrow]) .progress-wrapper {
|
||||
height: calc(100vh - 198px);
|
||||
}
|
||||
|
||||
.progress-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ha-circular-progress {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
|
@@ -21,7 +21,6 @@ import { computeRTL, emitRTLDirection } from "../../common/util/compute_rtl";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-icon";
|
||||
import { LogbookEntry } from "../../data/logbook";
|
||||
import { haStyleScrollbar } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-logbook")
|
||||
@@ -38,6 +37,9 @@ class HaLogbook extends LitElement {
|
||||
@property({ attribute: "rtl", type: Boolean })
|
||||
private _rtl = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "virtualize", reflect: true })
|
||||
public virtualize = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-icon" })
|
||||
public noIcon = false;
|
||||
|
||||
@@ -74,7 +76,7 @@ class HaLogbook extends LitElement {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="container ha-scrollbar ${classMap({
|
||||
class="container ${classMap({
|
||||
narrow: this.narrow,
|
||||
rtl: this._rtl,
|
||||
"no-name": this.noName,
|
||||
@@ -82,11 +84,15 @@ class HaLogbook extends LitElement {
|
||||
})}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
${scroll({
|
||||
items: this.entries,
|
||||
renderItem: (item: LogbookEntry, index?: number) =>
|
||||
this._renderLogbookItem(item, index),
|
||||
})}
|
||||
${this.virtualize
|
||||
? scroll({
|
||||
items: this.entries,
|
||||
renderItem: (item: LogbookEntry, index?: number) =>
|
||||
this._renderLogbookItem(item, index),
|
||||
})
|
||||
: this.entries.map((item, index) =>
|
||||
this._renderLogbookItem(item, index)
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -143,20 +149,23 @@ class HaLogbook extends LitElement {
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
<span class="item-message">${item.message}</span>
|
||||
<span>${item_username ? ` (${item_username})` : ``}</span>
|
||||
${!item.context_event_type
|
||||
${item.message}
|
||||
${item_username
|
||||
? ` by ${item_username}`
|
||||
: !item.context_event_type
|
||||
? ""
|
||||
: item.context_event_type === "call_service"
|
||||
? // Service Call
|
||||
html` by service
|
||||
` by service
|
||||
${item.context_domain}.${item.context_service}`
|
||||
: item.context_entity_id === item.entity_id
|
||||
? // HomeKit or something that self references
|
||||
html` by
|
||||
${item.context_name
|
||||
? item.context_name
|
||||
: item.context_event_type}`
|
||||
` by
|
||||
${
|
||||
item.context_name
|
||||
? item.context_name
|
||||
: item.context_event_type
|
||||
}`
|
||||
: // Another entity such as an automation or script
|
||||
html` by
|
||||
<a
|
||||
@@ -185,106 +194,104 @@ class HaLogbook extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rtl {
|
||||
direction: ltr;
|
||||
}
|
||||
.rtl {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.entry-container {
|
||||
width: 100%;
|
||||
}
|
||||
.entry-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.entry {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
line-height: 2em;
|
||||
padding: 8px 16px;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
.entry {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
line-height: 2em;
|
||||
padding: 8px 16px;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
.time {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 75px;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.time {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 75px;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.date {
|
||||
margin: 8px 0;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.date {
|
||||
margin: 8px 0;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.narrow .date {
|
||||
padding: 0 8px;
|
||||
}
|
||||
.narrow .date {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.rtl .date {
|
||||
direction: rtl;
|
||||
}
|
||||
.rtl .date {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.icon-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icon-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
}
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-icon {
|
||||
margin: 0 8px 0 16px;
|
||||
flex-shrink: 0;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-icon {
|
||||
margin: 0 8px 0 16px;
|
||||
flex-shrink: 0;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.message {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.message {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.no-name .item-message {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.no-name .message:first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.uni-virtualizer-host {
|
||||
display: block;
|
||||
position: relative;
|
||||
contain: strict;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.uni-virtualizer-host {
|
||||
display: block;
|
||||
position: relative;
|
||||
contain: strict;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.uni-virtualizer-host > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.uni-virtualizer-host > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.narrow .entry {
|
||||
flex-direction: column;
|
||||
line-height: 1.5;
|
||||
padding: 8px;
|
||||
}
|
||||
.narrow .entry {
|
||||
flex-direction: column;
|
||||
line-height: 1.5;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.narrow .icon-message ha-icon {
|
||||
margin-left: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
.narrow .icon-message ha-icon {
|
||||
margin-left: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,33 +1,33 @@
|
||||
import { mdiRefresh } from "@mdi/js";
|
||||
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 { computeRTL } from "../../common/util/compute_rtl";
|
||||
import "../../components/entity/ha-entity-picker";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import "./ha-logbook";
|
||||
import {
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { fetchUsers } from "../../data/user";
|
||||
import { fetchPersons } from "../../data/person";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import "../../components/entity/ha-entity-picker";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-date-range-picker";
|
||||
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-menu-button";
|
||||
import {
|
||||
clearLogbookCache,
|
||||
getLogbookData,
|
||||
LogbookEntry,
|
||||
} from "../../data/logbook";
|
||||
import { mdiRefresh } from "@mdi/js";
|
||||
import "../../components/ha-date-range-picker";
|
||||
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
||||
import { fetchPersons } from "../../data/person";
|
||||
import { fetchUsers } from "../../data/user";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-logbook";
|
||||
|
||||
@customElement("ha-panel-logbook")
|
||||
export class HaPanelLogbook extends LitElement {
|
||||
@@ -125,6 +125,7 @@ export class HaPanelLogbook extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.entries=${this._entries}
|
||||
.userIdToName=${this._userIdToName}
|
||||
virtualize
|
||||
></ha-logbook>`}
|
||||
</ha-app-layout>
|
||||
`;
|
||||
|
@@ -21,6 +21,7 @@ import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { computeActiveState } from "../../../common/entity/compute_active_state";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../../../common/entity/state_icon";
|
||||
@@ -36,7 +37,6 @@ import { hasAction } from "../common/has-action";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { ButtonCardConfig } from "./types";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
|
||||
@customElement("hui-button-card")
|
||||
export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
@@ -63,11 +63,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
|
||||
return {
|
||||
type: "button",
|
||||
tap_action: { action: "toggle" },
|
||||
hold_action: { action: "more-info" },
|
||||
show_icon: true,
|
||||
show_name: true,
|
||||
show_state: false,
|
||||
entity: foundEntities[0] || "",
|
||||
};
|
||||
}
|
||||
@@ -92,29 +87,18 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
this._config = {
|
||||
tap_action: {
|
||||
action:
|
||||
config.entity && DOMAINS_TOGGLE.has(computeDomain(config.entity))
|
||||
? "toggle"
|
||||
: "more-info",
|
||||
},
|
||||
hold_action: { action: "more-info" },
|
||||
double_tap_action: { action: "none" },
|
||||
show_icon: true,
|
||||
show_name: true,
|
||||
state_color: true,
|
||||
...config,
|
||||
};
|
||||
|
||||
if (config.entity && DOMAINS_TOGGLE.has(computeDomain(config.entity))) {
|
||||
this._config = {
|
||||
tap_action: {
|
||||
action: "toggle",
|
||||
},
|
||||
...this._config,
|
||||
};
|
||||
} else {
|
||||
this._config = {
|
||||
tap_action: {
|
||||
action: "more-info",
|
||||
},
|
||||
...this._config,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@@ -50,7 +50,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
["light", "switch", "sensor"]
|
||||
);
|
||||
|
||||
return { type: "entities", title: "My Title", entities: foundEntities };
|
||||
return { type: "entities", entities: foundEntities };
|
||||
}
|
||||
|
||||
@internalProperty() private _config?: EntitiesCardConfig;
|
||||
|
@@ -43,8 +43,8 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
background-color: #ef5350;
|
||||
color: white;
|
||||
background-color: var(--error-color);
|
||||
color: var(--color-on-error, white);
|
||||
padding: 8px;
|
||||
font-weight: 500;
|
||||
user-select: text;
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -20,18 +20,22 @@ import "../../../components/entity/state-badge";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import {
|
||||
ActionHandlerEvent,
|
||||
CallServiceActionConfig,
|
||||
MoreInfoActionConfig,
|
||||
} from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import "../components/hui-timestamp-display";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import "../components/hui-timestamp-display";
|
||||
import { GlanceCardConfig, GlanceConfigEntity } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
@customElement("hui-glance-card")
|
||||
export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
@@ -86,7 +90,14 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
state_color: true,
|
||||
...config,
|
||||
};
|
||||
const entities = processConfigEntities<GlanceConfigEntity>(config.entities);
|
||||
const entities = processConfigEntities<GlanceConfigEntity>(
|
||||
config.entities
|
||||
).map((entityConf) => {
|
||||
return {
|
||||
hold_action: { action: "more-info" } as MoreInfoActionConfig,
|
||||
...entityConf,
|
||||
};
|
||||
});
|
||||
|
||||
for (const entity of entities) {
|
||||
if (
|
||||
@@ -95,7 +106,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
!entity.tap_action.service) ||
|
||||
(entity.hold_action &&
|
||||
entity.hold_action.action === "call-service" &&
|
||||
!entity.hold_action.service)
|
||||
!(entity.hold_action as CallServiceActionConfig).service)
|
||||
) {
|
||||
throw new Error(
|
||||
'Missing required property "service" when tap_action or hold_action is call-service'
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import "../../../components/ha-icon-button";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@thomasloven/round-slider";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -20,7 +20,8 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../../../common/entity/state_icon";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-card";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../data/light";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { HomeAssistant, LightEntity } from "../../../types";
|
||||
@@ -32,7 +33,6 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LightCardConfig } from "./types";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
|
||||
@customElement("hui-light-card")
|
||||
export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
@@ -77,8 +77,9 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
this._config = {
|
||||
...config,
|
||||
tap_action: { action: "toggle" },
|
||||
hold_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
SUPPORT_BRIGHTNESS
|
||||
),
|
||||
"state-on": stateObj.state === "on",
|
||||
"state-unavailable": stateObj.state === "unavailable",
|
||||
"state-unavailable": stateObj.state === UNAVAILABLE,
|
||||
})}"
|
||||
.icon=${this._config.icon || stateIcon(stateObj)}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
|
@@ -14,7 +14,10 @@ import { classMap } from "lit-html/directives/class-map";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-markdown";
|
||||
import { subscribeRenderTemplate } from "../../../data/ws-templates";
|
||||
import {
|
||||
subscribeRenderTemplate,
|
||||
RenderTemplateResult,
|
||||
} from "../../../data/ws-templates";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type { MarkdownCardConfig } from "./types";
|
||||
@@ -40,7 +43,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@internalProperty() private _config?: MarkdownCardConfig;
|
||||
|
||||
@internalProperty() private _content = "";
|
||||
@internalProperty() private _templateResult?: RenderTemplateResult;
|
||||
|
||||
@internalProperty() private _unsubRenderTemplate?: Promise<UnsubscribeFunc>;
|
||||
|
||||
@@ -85,7 +88,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
class=${classMap({
|
||||
"no-header": !this._config.title,
|
||||
})}
|
||||
.content="${this._content}"
|
||||
.content="${this._templateResult?.result}"
|
||||
></ha-markdown>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -127,7 +130,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
this._unsubRenderTemplate = subscribeRenderTemplate(
|
||||
this.hass.connection,
|
||||
(result) => {
|
||||
this._content = result;
|
||||
this._templateResult = result;
|
||||
},
|
||||
{
|
||||
template: this._config.content,
|
||||
@@ -139,7 +142,10 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
);
|
||||
} catch (_err) {
|
||||
this._content = this._config!.content;
|
||||
this._templateResult = {
|
||||
result: this._config!.content,
|
||||
listeners: { all: false, domains: [], entities: [] },
|
||||
};
|
||||
this._unsubRenderTemplate = undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -28,10 +28,10 @@ import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import "../components/hui-image";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { PictureGlanceCardConfig, PictureGlanceEntityConfig } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
const STATES_OFF = new Set(["closed", "locked", "not_home", "off"]);
|
||||
|
||||
@@ -104,7 +104,10 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
});
|
||||
|
||||
this._config = config;
|
||||
this._config = {
|
||||
hold_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
@@ -225,6 +228,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
|
||||
entityConf = {
|
||||
tap_action: { action: dialog ? "more-info" : "toggle" },
|
||||
hold_action: { action: "more-info" },
|
||||
...entityConf,
|
||||
};
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import "../../../components/ha-icon-button";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@thomasloven/round-slider";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
query,
|
||||
svg,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { UNIT_F } from "../../../common/const";
|
||||
@@ -20,6 +20,8 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/ha-card";
|
||||
import type { HaCard } from "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import {
|
||||
ClimateEntity,
|
||||
CLIMATE_PRESET_NONE,
|
||||
@@ -28,14 +30,11 @@ import {
|
||||
} from "../../../data/climate";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { ThermostatCardConfig } from "./types";
|
||||
import type { HaCard } from "../../../components/ha-card";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
|
||||
const modeIcons: { [mode in HvacMode]: string } = {
|
||||
auto: "hass:calendar-sync",
|
||||
@@ -385,8 +384,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
class="${classMap({ "selected-icon": currentMode === mode })}"
|
||||
.mode="${mode}"
|
||||
.icon="${modeIcons[mode]}"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler()}
|
||||
@click=${this._handleAction}
|
||||
tabindex="0"
|
||||
></ha-icon-button>
|
||||
`;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
export interface Condition {
|
||||
entity: string;
|
||||
@@ -13,7 +14,7 @@ export function checkConditionsMet(
|
||||
return conditions.every((c) => {
|
||||
const state = hass.states[c.entity]
|
||||
? hass!.states[c.entity].state
|
||||
: "unavailable";
|
||||
: UNAVAILABLE;
|
||||
|
||||
return c.state ? state === c.state : state !== c.state_not;
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-service-picker";
|
||||
import {
|
||||
ActionConfig,
|
||||
@@ -20,43 +21,32 @@ import {
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EditorTarget } from "../editor/types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"action-changed": undefined;
|
||||
}
|
||||
// for add event listener
|
||||
interface HTMLElementEventMap {
|
||||
"action-changed": HASSDomEvent<undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hui-action-editor")
|
||||
export class HuiActionEditor extends LitElement {
|
||||
@property() public config?: ActionConfig;
|
||||
@property({ attribute: false }) public config?: ActionConfig;
|
||||
|
||||
@property() public label?: string;
|
||||
@property({ attribute: false }) public label?: string;
|
||||
|
||||
@property() public actions?: string[];
|
||||
@property({ attribute: false }) public actions?: string[];
|
||||
|
||||
@property() protected hass?: HomeAssistant;
|
||||
@property({ attribute: false }) protected hass?: HomeAssistant;
|
||||
|
||||
get _action(): string {
|
||||
return this.config!.action || "";
|
||||
return this.config?.action || "";
|
||||
}
|
||||
|
||||
get _navigation_path(): string {
|
||||
const config = this.config! as NavigateActionConfig;
|
||||
const config = this.config as NavigateActionConfig;
|
||||
return config.navigation_path || "";
|
||||
}
|
||||
|
||||
get _url_path(): string {
|
||||
const config = this.config! as UrlActionConfig;
|
||||
const config = this.config as UrlActionConfig;
|
||||
return config.url_path || "";
|
||||
}
|
||||
|
||||
get _service(): string {
|
||||
const config = this.config! as CallServiceActionConfig;
|
||||
const config = this.config as CallServiceActionConfig;
|
||||
return config.service || "";
|
||||
}
|
||||
|
||||
@@ -65,17 +55,19 @@ export class HuiActionEditor extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<paper-dropdown-menu
|
||||
.label="${this.label}"
|
||||
.configValue="${"action"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
>
|
||||
<paper-dropdown-menu .label=${this.label}>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected="${this.actions.indexOf(this._action)}"
|
||||
attr-for-selected="item-value"
|
||||
.configValue=${"action"}
|
||||
.selected=${this._action}
|
||||
@iron-select=${this._valueChanged}
|
||||
>
|
||||
<paper-item></paper-item>
|
||||
${this.actions.map((action) => {
|
||||
return html` <paper-item>${action}</paper-item> `;
|
||||
return html`
|
||||
<paper-item .itemValue=${action}>${action}</paper-item>
|
||||
`;
|
||||
})}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
@@ -83,9 +75,9 @@ export class HuiActionEditor extends LitElement {
|
||||
? html`
|
||||
<paper-input
|
||||
label="Navigation Path"
|
||||
.value="${this._navigation_path}"
|
||||
.configValue="${"navigation_path"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.value=${this._navigation_path}
|
||||
.configValue=${"navigation_path"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
@@ -93,9 +85,9 @@ export class HuiActionEditor extends LitElement {
|
||||
? html`
|
||||
<paper-input
|
||||
label="Url Path"
|
||||
.value="${this._url_path}"
|
||||
.configValue="${"url_path"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.value=${this._url_path}
|
||||
.configValue=${"url_path"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
@@ -103,30 +95,37 @@ export class HuiActionEditor extends LitElement {
|
||||
? html`
|
||||
<ha-service-picker
|
||||
.hass=${this.hass}
|
||||
.value="${this._service}"
|
||||
.configValue="${"service"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.value=${this._service}
|
||||
.configValue=${"service"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-service-picker>
|
||||
<h3>Toggle Editor to input Service Data</h3>
|
||||
<b>Service data can only be entered in the code editor</b>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event): void {
|
||||
if (!this.hass) {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!this.hass || !ev.detail.item?.itemValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (this[`_${target.configValue}`] === target.value) {
|
||||
|
||||
if (this[`_${target.configValue}`] === ev.detail.item.itemValue) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue === "action") {
|
||||
this.config = { action: "none" };
|
||||
}
|
||||
|
||||
if (target.configValue) {
|
||||
this.config = { ...this.config!, [target.configValue!]: target.value };
|
||||
fireEvent(this, "action-changed");
|
||||
const newConfig =
|
||||
target.configValue === "action"
|
||||
? { action: ev.detail.item.itemValue }
|
||||
: {
|
||||
...this.config!,
|
||||
[target.configValue!]: ev.detail.item.itemValue,
|
||||
};
|
||||
fireEvent(this, "value-changed", { value: newConfig });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,10 +18,10 @@ import Sortable, {
|
||||
} from "sortablejs/modular/sortable.core.esm";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { sortableStyles } from "../../../resources/ha-sortable-style";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EditorTarget } from "../editor/types";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
|
||||
@customElement("hui-entity-editor")
|
||||
@@ -73,7 +73,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${entityConf.entity}
|
||||
.index=${index}
|
||||
@change=${this._valueChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
</div>
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
@change=${this._addEntity}
|
||||
@value-changed=${this._addEntity}
|
||||
></ha-entity-picker>
|
||||
`;
|
||||
}
|
||||
@@ -136,15 +136,15 @@ export class HuiEntityEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _addEntity(ev: Event): Promise<void> {
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (target.value === "") {
|
||||
private async _addEntity(ev: CustomEvent): Promise<void> {
|
||||
const value = ev.detail.value;
|
||||
if (value === "") {
|
||||
return;
|
||||
}
|
||||
const newConfigEntities = this.entities!.concat({
|
||||
entity: target.value as string,
|
||||
entity: value as string,
|
||||
});
|
||||
target.value = "";
|
||||
(ev.target as HaEntityPicker).value = "";
|
||||
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
||||
}
|
||||
|
||||
@@ -160,16 +160,17 @@ export class HuiEntityEditor extends LitElement {
|
||||
fireEvent(this, "entities-changed", { entities: newEntities });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event): void {
|
||||
const target = ev.target! as EditorTarget;
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const value = ev.detail.value;
|
||||
const index = (ev.target as any).index;
|
||||
const newConfigEntities = this.entities!.concat();
|
||||
|
||||
if (target.value === "") {
|
||||
newConfigEntities.splice(target.index!, 1);
|
||||
if (value === "") {
|
||||
newConfigEntities.splice(index, 1);
|
||||
} else {
|
||||
newConfigEntities[target.index!] = {
|
||||
...newConfigEntities[target.index!],
|
||||
entity: target.value!,
|
||||
newConfigEntities[index] = {
|
||||
...newConfigEntities[index],
|
||||
entity: value!,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
@@ -16,6 +16,7 @@ import { STATES_OFF } from "../../../common/const";
|
||||
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
|
||||
import "../../../components/ha-camera-stream";
|
||||
import { fetchThumbnailUrlWithCache } from "../../../data/camera";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { CameraEntity, HomeAssistant } from "../../../types";
|
||||
|
||||
const UPDATE_INTERVAL = 10000;
|
||||
@@ -73,7 +74,7 @@ export class HuiImage extends LitElement {
|
||||
}
|
||||
const ratio = this.aspectRatio ? parseAspectRatio(this.aspectRatio) : null;
|
||||
const stateObj = this.entity ? this.hass.states[this.entity] : undefined;
|
||||
const state = stateObj ? stateObj.state : "unavailable";
|
||||
const state = stateObj ? stateObj.state : UNAVAILABLE;
|
||||
|
||||
// Figure out image source to use
|
||||
let imageSrc: string | undefined;
|
||||
@@ -131,8 +132,9 @@ export class HuiImage extends LitElement {
|
||||
${this.cameraImage && this.cameraView === "live"
|
||||
? html`
|
||||
<ha-camera-stream
|
||||
muted
|
||||
.hass=${this.hass}
|
||||
.stateObj="${cameraObj}"
|
||||
.stateObj=${cameraObj}
|
||||
></ha-camera-stream>
|
||||
`
|
||||
: html`
|
||||
|
@@ -2,14 +2,18 @@ import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { assert, boolean, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stateIcon } from "../../../../common/entity/state_icon";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import "../../../../components/ha-switch";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { ButtonCardConfig } from "../../cards/types";
|
||||
@@ -17,16 +21,8 @@ import "../../components/hui-action-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-formfield";
|
||||
import { actionConfigStruct, EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import { assert, object, string, optional, boolean } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -63,11 +59,11 @@ export class HuiButtonCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _show_name(): boolean {
|
||||
return this._config!.show_name || true;
|
||||
return this._config!.show_name ?? true;
|
||||
}
|
||||
|
||||
get _show_state(): boolean {
|
||||
return this._config!.show_state || false;
|
||||
return this._config!.show_state ?? false;
|
||||
}
|
||||
|
||||
get _icon(): string {
|
||||
@@ -75,7 +71,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _show_icon(): boolean {
|
||||
return this._config!.show_icon || true;
|
||||
return this._config!.show_icon ?? true;
|
||||
}
|
||||
|
||||
get _icon_height(): string {
|
||||
@@ -85,11 +81,11 @@ export class HuiButtonCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _tap_action(): ActionConfig {
|
||||
return this._config!.tap_action || { action: "more-info" };
|
||||
return this._config!.tap_action || { action: "toggle" };
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "none" };
|
||||
return this._config!.hold_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
@@ -123,7 +119,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<div class="side-by-side">
|
||||
@@ -161,7 +157,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked="${this._show_name !== false}"
|
||||
.configValue="${"show_name"}"
|
||||
@change="${this._valueChanged}"
|
||||
@change="${this._change}"
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@@ -175,7 +171,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked=${this._show_state !== false}
|
||||
.configValue=${"show_state"}
|
||||
@change=${this._valueChanged}
|
||||
@change=${this._change}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@@ -189,7 +185,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked="${this._show_icon !== false}"
|
||||
.configValue="${"show_icon"}"
|
||||
@change="${this._valueChanged}"
|
||||
@change="${this._change}"
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@@ -225,7 +221,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
.label="${this.hass.localize(
|
||||
@@ -237,27 +233,43 @@ export class HuiButtonCardEditor extends LitElement
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _change(ev: Event) {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = target.checked;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
@@ -266,18 +278,11 @@ export class HuiButtonCardEditor extends LitElement
|
||||
target.configValue === "icon_height" &&
|
||||
!isNaN(Number(target.value))
|
||||
) {
|
||||
newValue = `${String(target.value)}px`;
|
||||
newValue = `${String(value)}px`;
|
||||
}
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]:
|
||||
target.checked !== undefined
|
||||
? target.checked
|
||||
: newValue !== undefined
|
||||
? newValue
|
||||
: target.value
|
||||
? target.value
|
||||
: target.config,
|
||||
[target.configValue!]: newValue !== undefined ? newValue : value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -2,11 +2,12 @@ import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stateIcon } from "../../../../common/entity/state_icon";
|
||||
import "../../../../components/ha-icon-input";
|
||||
@@ -17,13 +18,8 @@ import "../../components/hui-action-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import { actionConfigStruct, EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { string, object, optional, assert } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -66,11 +62,11 @@ export class HuiLightCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "none" };
|
||||
return this._config!.hold_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _double_tap_action(): ActionConfig {
|
||||
return this._config!.double_tap_action || { action: "none" };
|
||||
get _double_tap_action(): ActionConfig | undefined {
|
||||
return this._config!.double_tap_action;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -100,7 +96,7 @@ export class HuiLightCardEditor extends LitElement
|
||||
.value=${this._entity}
|
||||
.configValue=${"entity"}
|
||||
.includeDomains=${includeDomains}
|
||||
@change=${this._valueChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<div class="side-by-side">
|
||||
@@ -145,7 +141,7 @@ export class HuiLightCardEditor extends LitElement
|
||||
.config=${this._hold_action}
|
||||
.actions=${actions}
|
||||
.configValue=${"hold_action"}
|
||||
@action-changed=${this._valueChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-action-editor>
|
||||
|
||||
<hui-action-editor
|
||||
@@ -158,32 +154,30 @@ export class HuiLightCardEditor extends LitElement
|
||||
.config=${this._double_tap_action}
|
||||
.actions=${actions}
|
||||
.configValue=${"double_tap_action"}
|
||||
@action-changed=${this._valueChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-action-editor>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: target.value ? target.value : target.config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -2,11 +2,12 @@ import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -14,13 +15,8 @@ import { PictureCardConfig } from "../../cards/types";
|
||||
import "../../components/hui-action-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import { actionConfigStruct, EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { string, object, optional, assert } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -89,7 +85,7 @@ export class HuiPictureCardEditor extends LitElement
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
.label="${this.hass.localize(
|
||||
@@ -101,7 +97,7 @@ export class HuiPictureCardEditor extends LitElement
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
@@ -114,26 +110,24 @@ export class HuiPictureCardEditor extends LitElement
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: target.value ? target.value : target.config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -5,14 +5,16 @@ import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { assert, boolean, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-switch";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-switch";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { PictureEntityCardConfig } from "../../cards/types";
|
||||
@@ -20,14 +22,8 @@ import "../../components/hui-action-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import { actionConfigStruct, EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import { assert, object, string, optional, boolean } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -86,16 +82,16 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
return this._config!.tap_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "more-info" };
|
||||
get _hold_action(): ActionConfig | undefined {
|
||||
return this._config!.hold_action;
|
||||
}
|
||||
|
||||
get _show_name(): boolean {
|
||||
return this._config!.show_name || true;
|
||||
return this._config!.show_name ?? true;
|
||||
}
|
||||
|
||||
get _show_state(): boolean {
|
||||
return this._config!.show_state || true;
|
||||
return this._config!.show_state ?? true;
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
@@ -123,7 +119,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<paper-input
|
||||
@@ -155,7 +151,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._camera_image}"
|
||||
.configValue=${"camera_image"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.includeDomains=${includeDomains}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
@@ -184,8 +180,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
type="number"
|
||||
.value="${Number(this._aspect_ratio.replace("%", ""))}"
|
||||
.value="${this._aspect_ratio}"
|
||||
.configValue="${"aspect_ratio"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
@@ -201,7 +196,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked="${this._config!.show_name !== false}"
|
||||
.configValue="${"show_name"}"
|
||||
@change="${this._valueChanged}"
|
||||
@change="${this._change}"
|
||||
></ha-switch
|
||||
></ha-formfield>
|
||||
</div>
|
||||
@@ -215,7 +210,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked="${this._config!.show_state !== false}"
|
||||
.configValue="${"show_state"}"
|
||||
@change="${this._valueChanged}"
|
||||
@change="${this._change}"
|
||||
></ha-switch
|
||||
></ha-formfield>
|
||||
</div>
|
||||
@@ -231,7 +226,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
.label="${this.hass.localize(
|
||||
@@ -243,7 +238,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
@@ -256,34 +251,43 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _change(ev: Event) {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
let value = target.value;
|
||||
const value = target.checked;
|
||||
|
||||
if (target.configValue! === "aspect_ratio" && target.value) {
|
||||
value += "%";
|
||||
}
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.configValue) {
|
||||
if (value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]:
|
||||
target.checked !== undefined
|
||||
? target.checked
|
||||
: value || target.config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -5,11 +5,12 @@ import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { array, assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
@@ -25,10 +26,8 @@ import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
entitiesConfigStruct,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { assert, string, object, optional, array } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -92,21 +91,13 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _tap_action(): ActionConfig {
|
||||
return this._config!.tap_action || { action: "more-info" };
|
||||
return this._config!.tap_action || { action: "toggle" };
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _show_name(): boolean {
|
||||
return this._config!.show_name || false;
|
||||
}
|
||||
|
||||
get _show_state(): boolean {
|
||||
return this._config!.show_state || false;
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "";
|
||||
}
|
||||
@@ -151,7 +142,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._camera_image}"
|
||||
.configValue=${"camera_image"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
.includeDomains=${includeDomains}
|
||||
></ha-entity-picker>
|
||||
@@ -180,8 +171,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
type="number"
|
||||
.value="${Number(this._aspect_ratio.replace("%", ""))}"
|
||||
.value="${this._aspect_ratio}"
|
||||
.configValue="${"aspect_ratio"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
@@ -195,7 +185,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<div class="side-by-side">
|
||||
@@ -209,7 +199,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
.label="${this.hass.localize(
|
||||
@@ -221,7 +211,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
</div>
|
||||
<hui-entity-editor
|
||||
@@ -239,36 +229,29 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
let value = target.value;
|
||||
|
||||
if (target.configValue! === "aspect_ratio" && target.value) {
|
||||
value += "%";
|
||||
}
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (ev.detail && ev.detail.entities) {
|
||||
this._config = { ...this._config, entities: ev.detail.entities };
|
||||
|
||||
this._configEntities = processEditorEntities(this._config.entities);
|
||||
} else if (target.configValue) {
|
||||
if (
|
||||
this[`_${target.configValue}`] === value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: value || target.config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -3,8 +3,8 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
@@ -28,7 +28,7 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Invalid Configuration: 'icon' required");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
@@ -29,12 +29,13 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Error in element configuration");
|
||||
}
|
||||
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.toggle(
|
||||
"clickable",
|
||||
config.tap_action && config.tap_action.action !== "none"
|
||||
this._config.tap_action && this._config.tap_action.action !== "none"
|
||||
);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -16,9 +16,9 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceElement, StateBadgeElementConfig } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
@customElement("hui-state-badge-element")
|
||||
export class HuiStateBadgeElement extends LitElement
|
||||
@@ -32,7 +32,7 @@ export class HuiStateBadgeElement extends LitElement
|
||||
throw Error("Invalid Configuration: 'entity' required");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -18,9 +18,9 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceElement, StateIconElementConfig } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
@customElement("hui-state-icon-element")
|
||||
export class HuiStateIconElement extends LitElement implements LovelaceElement {
|
||||
@@ -33,7 +33,11 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Invalid Configuration: 'entity' required");
|
||||
}
|
||||
|
||||
this._config = { state_color: true, ...config };
|
||||
this._config = {
|
||||
state_color: true,
|
||||
hold_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -18,9 +18,9 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceElement, StateLabelElementConfig } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
@customElement("hui-state-label-element")
|
||||
class HuiStateLabelElement extends LitElement implements LovelaceElement {
|
||||
@@ -33,7 +33,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Invalid Configuration: 'entity' required");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@@ -22,6 +23,19 @@ class HuiGroupEntityRow extends LitElement implements LovelaceRow {
|
||||
|
||||
@internalProperty() private _config?: EntityConfig;
|
||||
|
||||
private _computeCanToggle(hass: HomeAssistant, entityIds: string[]): boolean {
|
||||
return entityIds.some((entityId) => {
|
||||
const domain = computeDomain(entityId);
|
||||
if (domain === "group") {
|
||||
return this._computeCanToggle(
|
||||
hass,
|
||||
this.hass?.states[entityId].attributes["entity_id"]
|
||||
);
|
||||
}
|
||||
return DOMAINS_TOGGLE.has(domain);
|
||||
});
|
||||
}
|
||||
|
||||
public setConfig(config: EntityConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Configuration error");
|
||||
@@ -50,7 +64,7 @@ class HuiGroupEntityRow extends LitElement implements LovelaceRow {
|
||||
|
||||
return html`
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
${this._computeCanToggle(stateObj.attributes.entity_id)
|
||||
${this._computeCanToggle(this.hass, stateObj.attributes.entity_id)
|
||||
? html`
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
@@ -69,12 +83,6 @@ class HuiGroupEntityRow extends LitElement implements LovelaceRow {
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeCanToggle(entityIds): boolean {
|
||||
return entityIds.some((entityId) =>
|
||||
DOMAINS_TOGGLE.has(entityId.split(".", 1)[0])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -11,7 +11,7 @@ import "../../../components/ha-date-input";
|
||||
import type { HaDateInput } from "../../../components/ha-date-input";
|
||||
import "../../../components/paper-time-input";
|
||||
import type { PaperTimeInput } from "../../../components/paper-time-input";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||
import { setInputDateTimeValue } from "../../../data/input_datetime";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
@@ -70,10 +70,10 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
? html`
|
||||
<paper-time-input
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
.hour=${stateObj.state === "unknown"
|
||||
.hour=${stateObj.state === UNKNOWN
|
||||
? ""
|
||||
: ("0" + stateObj.attributes.hour).slice(-2)}
|
||||
.min=${stateObj.state === "unknown"
|
||||
.min=${stateObj.state === UNKNOWN
|
||||
? ""
|
||||
: ("0" + stateObj.attributes.minute).slice(-2)}
|
||||
.amPm=${false}
|
||||
|
@@ -22,6 +22,7 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
|
||||
interface SensorEntityConfig extends EntitiesCardEntityConfig {
|
||||
format?: "relative" | "date" | "time" | "datetime";
|
||||
@@ -71,8 +72,7 @@ class HuiSensorEntityRow extends LitElement implements LovelaceRow {
|
||||
>
|
||||
${stateObj.attributes.device_class ===
|
||||
SENSOR_DEVICE_CLASS_TIMESTAMP &&
|
||||
stateObj.state !== "unavailable" &&
|
||||
stateObj.state !== "unknown"
|
||||
!UNAVAILABLE_STATES.includes(stateObj.state)
|
||||
? html`
|
||||
<hui-timestamp-display
|
||||
.hass=${this.hass}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiMicrophone,
|
||||
mdiPlus,
|
||||
mdiClose,
|
||||
mdiPencil,
|
||||
mdiDotsVertical,
|
||||
mdiHelpCircle,
|
||||
mdiMicrophone,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-scroll-effects/effects/waterfall";
|
||||
@@ -17,9 +18,9 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -28,16 +29,17 @@ import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import scrollToTarget from "../../common/dom/scroll-to-target";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { afterNextRender } from "../../common/util/render-status";
|
||||
import "../../components/ha-button-menu";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/ha-icon-button-arrow-next";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../components/ha-svg-icon";
|
||||
import type {
|
||||
LovelaceConfig,
|
||||
LovelacePanelConfig,
|
||||
@@ -58,8 +60,6 @@ import type { Lovelace } from "./types";
|
||||
import "./views/hui-panel-view";
|
||||
import type { HUIPanelView } from "./views/hui-panel-view";
|
||||
import { HUIView } from "./views/hui-view";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
|
||||
|
||||
class HUIRoot extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -382,6 +382,15 @@ class HUIRoot extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _isVisible = (view: LovelaceViewConfig) =>
|
||||
Boolean(
|
||||
this._editMode ||
|
||||
view.visible === undefined ||
|
||||
view.visible === true ||
|
||||
(Array.isArray(view.visible) &&
|
||||
view.visible.some((show) => show.user === this.hass!.user?.id))
|
||||
);
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
@@ -407,9 +416,14 @@ class HUIRoot extends LitElement {
|
||||
|
||||
if (changedProperties.has("route")) {
|
||||
const views = this.config.views;
|
||||
|
||||
if (!viewPath && views.length) {
|
||||
navigate(this, `${this.route!.prefix}/${views[0].path || 0}`, true);
|
||||
newSelectView = 0;
|
||||
newSelectView = views.findIndex(this._isVisible);
|
||||
navigate(
|
||||
this,
|
||||
`${this.route!.prefix}/${views[newSelectView].path || newSelectView}`,
|
||||
true
|
||||
);
|
||||
} else if (viewPath === "hass-unused-entities") {
|
||||
newSelectView = "hass-unused-entities";
|
||||
} else if (viewPath) {
|
||||
@@ -449,8 +463,14 @@ class HUIRoot extends LitElement {
|
||||
this.lovelace!.mode === "storage" &&
|
||||
viewPath === "hass-unused-entities"
|
||||
) {
|
||||
navigate(this, `${this.route?.prefix}/${views[0]?.path || 0}`);
|
||||
newSelectView = 0;
|
||||
newSelectView = views.findIndex(this._isVisible);
|
||||
navigate(
|
||||
this,
|
||||
`${this.route!.prefix}/${
|
||||
views[newSelectView].path || newSelectView
|
||||
}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiPlayNetwork } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import {
|
||||
@@ -46,9 +45,9 @@ class PanelMediaBrowser extends LitElement {
|
||||
|
||||
const title =
|
||||
this._entityId === BROWSER_SOURCE
|
||||
? `${this.hass.localize("ui.components.media-browser.web-browser")} - `
|
||||
? `${this.hass.localize("ui.components.media-browser.web-browser")}`
|
||||
: stateObj?.attributes.friendly_name
|
||||
? `${stateObj?.attributes.friendly_name} - `
|
||||
? `${stateObj?.attributes.friendly_name}`
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
@@ -59,17 +58,17 @@ class PanelMediaBrowser extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
<div main-title>
|
||||
${title || ""}${this.hass.localize(
|
||||
"ui.components.media-browser.media-player-browser"
|
||||
)}
|
||||
</div>
|
||||
<mwc-button @click=${this._showSelectMediaPlayerDialog}>
|
||||
<ha-svg-icon .path=${mdiPlayNetwork}></ha-svg-icon>
|
||||
<div main-title class="heading">
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.components.media-browser.choose_player"
|
||||
"ui.components.media-browser.media-player-browser"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
<div class="secondary-text">${title || ""}</div>
|
||||
</div>
|
||||
<mwc-button @click=${this._showSelectMediaPlayerDialog}>
|
||||
${this.hass.localize("ui.components.media-browser.choose_player")}
|
||||
</mwc-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="content">
|
||||
@@ -134,9 +133,24 @@ class PanelMediaBrowser extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
--mdc-theme-primary: var(--app-header-text-color);
|
||||
}
|
||||
ha-media-player-browse {
|
||||
height: calc(100vh - 84px);
|
||||
}
|
||||
:host([narrow]) app-toolbar mwc-button {
|
||||
width: 65px;
|
||||
}
|
||||
.heading {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.heading .secondary-text {
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -10,10 +10,12 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import { BROWSER_SOURCE } from "../../data/media-player";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { SelectMediaPlayerDialogParams } from "./show-select-media-source-dialog";
|
||||
|
||||
@customElement("hui-dialog-select-media-player")
|
||||
@@ -55,13 +57,15 @@ export class HuiDialogSelectMediaPlayer extends LitElement {
|
||||
"ui.components.media-browser.web-browser"
|
||||
)}</paper-item
|
||||
>
|
||||
${this._params.mediaSources.map(
|
||||
(source) => html`
|
||||
<paper-item .itemName=${source.entity_id}
|
||||
>${source.attributes.friendly_name}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
${this._params.mediaSources
|
||||
.sort((a, b) => compare(computeStateName(a), computeStateName(b)))
|
||||
.map(
|
||||
(source) => html`
|
||||
<paper-item .itemName=${source.entity_id}
|
||||
>${computeStateName(source)}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-dialog>
|
||||
`;
|
||||
|
@@ -1,171 +0,0 @@
|
||||
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 { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-icon-button";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import "../../styles/polymer-ha-style";
|
||||
import "../../components/ha-settings-row";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaLongLivedTokens extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
.card-content {
|
||||
margin: -1em 0;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
</style>
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.profile.long_lived_access_tokens.header')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
[[localize('ui.panel.profile.long_lived_access_tokens.description')]]
|
||||
<a
|
||||
href="https://developers.home-assistant.io/docs/en/auth_api.html#making-authenticated-requests"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
[[localize('ui.panel.profile.long_lived_access_tokens.learn_auth_requests')]]
|
||||
</a>
|
||||
</p>
|
||||
<template is="dom-if" if="[[!_tokens.length]]">
|
||||
<p>
|
||||
[[localize('ui.panel.profile.long_lived_access_tokens.empty_state')]]
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<template is="dom-repeat" items="[[_tokens]]">
|
||||
<ha-settings-row two-line>
|
||||
<span slot="heading">[[item.client_name]]</span>
|
||||
<div slot="description">[[_formatCreatedAt(item.created_at)]]</div>
|
||||
<ha-icon-button
|
||||
icon="hass:delete"
|
||||
on-click="_handleDelete"
|
||||
></ha-icon-button>
|
||||
</ha-settings-row>
|
||||
</template>
|
||||
<div class="card-actions">
|
||||
<mwc-button on-click="_handleCreate">
|
||||
[[localize('ui.panel.profile.long_lived_access_tokens.create')]]
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
refreshTokens: Array,
|
||||
_tokens: {
|
||||
type: Array,
|
||||
computed: "_computeTokens(refreshTokens)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_computeTokens(refreshTokens) {
|
||||
return refreshTokens
|
||||
.filter((tkn) => tkn.type === "long_lived_access_token")
|
||||
.reverse();
|
||||
}
|
||||
|
||||
_formatTitle(name) {
|
||||
return this.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.token_title",
|
||||
"name",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
_formatCreatedAt(created) {
|
||||
return this.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.created_at",
|
||||
"date",
|
||||
formatDateTime(new Date(created), this.hass.language)
|
||||
);
|
||||
}
|
||||
|
||||
async _handleCreate() {
|
||||
const name = await showPromptDialog(this, {
|
||||
text: this.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.prompt_name"
|
||||
),
|
||||
});
|
||||
if (!name) return;
|
||||
try {
|
||||
const token = await this.hass.callWS({
|
||||
type: "auth/long_lived_access_token",
|
||||
lifespan: 3650,
|
||||
client_name: name,
|
||||
});
|
||||
await showPromptDialog(this, {
|
||||
title: name,
|
||||
text: this.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.prompt_copy_token"
|
||||
),
|
||||
defaultValue: token,
|
||||
});
|
||||
this.fire("hass-refresh-tokens");
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
showAlertDialog(this, {
|
||||
text: this.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.create_failed"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async _handleDelete(ev) {
|
||||
const token = ev.model.item;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.confirm_delete",
|
||||
"name",
|
||||
token.client_name
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.hass.callWS({
|
||||
type: "auth/delete_refresh_token",
|
||||
refresh_token_id: token.id,
|
||||
});
|
||||
this.fire("hass-refresh-tokens");
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
showAlertDialog(this, {
|
||||
text: this.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.delete_failed"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-long-lived-access-tokens-card", HaLongLivedTokens);
|
197
src/panels/profile/ha-long-lived-access-tokens-card.ts
Normal file
197
src/panels/profile/ha-long-lived-access-tokens-card.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import relativeTime from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-settings-row";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { RefreshToken } from "../../data/refresh_token";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
showPromptDialog,
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import "../../styles/polymer-ha-style";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-long-lived-access-tokens-card")
|
||||
class HaLongLivedTokens extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public refreshTokens?: RefreshToken[];
|
||||
|
||||
private _accessTokens = memoizeOne(
|
||||
(refreshTokens: RefreshToken[]): RefreshToken[] =>
|
||||
refreshTokens
|
||||
?.filter((token) => token.type === "long_lived_access_token")
|
||||
.reverse()
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const accessTokens = this._accessTokens(this.refreshTokens!);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.header"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.description"
|
||||
)}
|
||||
|
||||
<a
|
||||
href="https://developers.home-assistant.io/docs/en/auth_api.html#making-authenticated-requests"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.learn_auth_requests"
|
||||
)}
|
||||
</a>
|
||||
${!accessTokens?.length
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.empty_state"
|
||||
)}
|
||||
</p>`
|
||||
: accessTokens!.map(
|
||||
(token) => html`<ha-settings-row two-line>
|
||||
<span slot="heading">${token.client_name}</span>
|
||||
<div slot="description">
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.created",
|
||||
"date",
|
||||
relativeTime(
|
||||
new Date(token.created_at),
|
||||
this.hass.localize
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
.token=${token}
|
||||
.disabled=${token.is_current}
|
||||
.title=${this.hass.localize(`ui.common.delete`)}
|
||||
@click=${this._deleteToken}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-button>
|
||||
</ha-settings-row>`
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._createToken}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.create"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _createToken(): Promise<void> {
|
||||
const name = await showPromptDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.prompt_name"
|
||||
),
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.name"
|
||||
),
|
||||
});
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = await this.hass.callWS<string>({
|
||||
type: "auth/long_lived_access_token",
|
||||
lifespan: 3650,
|
||||
client_name: name,
|
||||
});
|
||||
|
||||
showPromptDialog(this, {
|
||||
title: name,
|
||||
text: this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.prompt_copy_token"
|
||||
),
|
||||
defaultValue: token,
|
||||
});
|
||||
|
||||
fireEvent(this, "hass-refresh-tokens");
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.create_failed"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteToken(ev: Event): Promise<void> {
|
||||
const token = (ev.currentTarget as any).token;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.confirm_delete",
|
||||
"name",
|
||||
token.client_name
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.hass.callWS({
|
||||
type: "auth/delete_refresh_token",
|
||||
refresh_token_id: token.id,
|
||||
});
|
||||
fireEvent(this, "hass-refresh-tokens");
|
||||
} catch (err) {
|
||||
await showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.delete_failed"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
mwc-button {
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-long-lived-access-tokens-card": HaLongLivedTokens;
|
||||
}
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
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 "@polymer/paper-item/paper-item";
|
||||
@@ -9,9 +8,9 @@ import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -22,7 +21,9 @@ import {
|
||||
CoreFrontendUserData,
|
||||
getOptimisticFrontendUserDataCollection,
|
||||
} from "../../data/frontend";
|
||||
import { RefreshToken } from "../../data/refresh_token";
|
||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-advanced-mode-row";
|
||||
@@ -35,15 +36,15 @@ import "./ha-pick-language-row";
|
||||
import "./ha-pick-theme-row";
|
||||
import "./ha-push-notifications-row";
|
||||
import "./ha-refresh-tokens-card";
|
||||
import "./ha-set-vibrate-row";
|
||||
import "./ha-set-suspend-row";
|
||||
import "./ha-set-vibrate-row";
|
||||
|
||||
class HaPanelProfile extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@internalProperty() private _refreshTokens?: unknown[];
|
||||
@internalProperty() private _refreshTokens?: RefreshToken[];
|
||||
|
||||
@internalProperty() private _coreUserData?: CoreFrontendUserData | null;
|
||||
|
||||
@@ -106,6 +107,23 @@ class HaPanelProfile extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
></ha-pick-dashboard-row>
|
||||
<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.customize_sidebar.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.customize_sidebar.description"
|
||||
)}
|
||||
</span>
|
||||
<mwc-button @click=${this._customizeSidebar}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.customize_sidebar.button"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-settings-row>
|
||||
${this.hass.dockedSidebar !== "auto" || !this.narrow
|
||||
? html`
|
||||
<ha-force-narrow-row
|
||||
@@ -182,6 +200,10 @@ class HaPanelProfile extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _customizeSidebar() {
|
||||
fireEvent(this, "hass-edit-sidebar");
|
||||
}
|
||||
|
||||
private async _refreshRefreshTokens() {
|
||||
this._refreshTokens = await this.hass.callWS({
|
||||
type: "auth/refresh_tokens",
|
||||
|
@@ -1,126 +0,0 @@
|
||||
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 "../../components/ha-icon-button";
|
||||
import "../../components/ha-settings-row";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaRefreshTokens extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
ha-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-icon-button[disabled] {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
</style>
|
||||
<ha-card header="[[localize('ui.panel.profile.refresh_tokens.header')]]">
|
||||
<div class="card-content">
|
||||
[[localize('ui.panel.profile.refresh_tokens.description')]]
|
||||
</div>
|
||||
<template is="dom-repeat" items="[[_computeTokens(refreshTokens)]]">
|
||||
<ha-settings-row three-line>
|
||||
<span slot="heading">[[_formatTitle(item.client_id)]]</span>
|
||||
<div slot="description">[[_formatCreatedAt(item.created_at)]]</div>
|
||||
<div slot="description">[[_formatLastUsed(item)]]</div>
|
||||
<div>
|
||||
<template is="dom-if" if="[[item.is_current]]">
|
||||
<paper-tooltip animation-delay="0" position="left"
|
||||
>[[localize('ui.panel.profile.refresh_tokens.current_token_tooltip')]]</paper-tooltip
|
||||
>
|
||||
</template>
|
||||
<ha-icon-button
|
||||
icon="hass:delete"
|
||||
on-click="_handleDelete"
|
||||
disabled="[[item.is_current]]"
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</ha-settings-row>
|
||||
</template>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
refreshTokens: Array,
|
||||
};
|
||||
}
|
||||
|
||||
_computeTokens(refreshTokens) {
|
||||
return refreshTokens.filter((tkn) => tkn.type === "normal").reverse();
|
||||
}
|
||||
|
||||
_formatTitle(clientId) {
|
||||
return this.localize(
|
||||
"ui.panel.profile.refresh_tokens.token_title",
|
||||
"clientId",
|
||||
clientId
|
||||
);
|
||||
}
|
||||
|
||||
_formatCreatedAt(created) {
|
||||
return this.localize(
|
||||
"ui.panel.profile.refresh_tokens.created_at",
|
||||
"date",
|
||||
formatDateTime(new Date(created), this.hass.language)
|
||||
);
|
||||
}
|
||||
|
||||
_formatLastUsed(item) {
|
||||
return item.last_used_at
|
||||
? this.localize(
|
||||
"ui.panel.profile.refresh_tokens.last_used",
|
||||
"date",
|
||||
formatDateTime(new Date(item.last_used_at), this.hass.language),
|
||||
"location",
|
||||
item.last_used_ip
|
||||
)
|
||||
: this.localize("ui.panel.profile.refresh_tokens.not_used");
|
||||
}
|
||||
|
||||
async _handleDelete(ev) {
|
||||
const token = ev.model.item;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.localize(
|
||||
"ui.panel.profile.refresh_tokens.confirm_delete",
|
||||
"name",
|
||||
token.client_id
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.hass.callWS({
|
||||
type: "auth/delete_refresh_token",
|
||||
refresh_token_id: token.id,
|
||||
});
|
||||
this.fire("hass-refresh-tokens");
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
showAlertDialog(this, {
|
||||
text: this.localize("ui.panel.profile.refresh_tokens.delete_failed"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-refresh-tokens-card", HaRefreshTokens);
|
150
src/panels/profile/ha-refresh-tokens-card.ts
Normal file
150
src/panels/profile/ha-refresh-tokens-card.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import relativeTime from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-settings-row";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { RefreshToken } from "../../data/refresh_token";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-refresh-tokens-card")
|
||||
class HaRefreshTokens extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public refreshTokens?: RefreshToken[];
|
||||
|
||||
private _refreshTokens = memoizeOne(
|
||||
(refreshTokens: RefreshToken[]): RefreshToken[] =>
|
||||
refreshTokens?.filter((token) => token.type === "normal").reverse()
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const refreshTokens = this._refreshTokens(this.refreshTokens!);
|
||||
return html`<ha-card
|
||||
.header=${this.hass.localize("ui.panel.profile.refresh_tokens.header")}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this.hass.localize("ui.panel.profile.refresh_tokens.description")}
|
||||
${refreshTokens?.length
|
||||
? refreshTokens!.map(
|
||||
(token) => html`<ha-settings-row three-line>
|
||||
<span slot="heading"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.token_title",
|
||||
"clientId",
|
||||
token.client_id
|
||||
)}
|
||||
</span>
|
||||
<div slot="description">
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.created_at",
|
||||
"date",
|
||||
relativeTime(new Date(token.created_at), this.hass.localize)
|
||||
)}
|
||||
</div>
|
||||
<div slot="description">
|
||||
${token.last_used_at
|
||||
? this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.last_used",
|
||||
"date",
|
||||
relativeTime(
|
||||
new Date(token.last_used_at),
|
||||
this.hass.localize
|
||||
),
|
||||
"location",
|
||||
token.last_used_ip
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.not_used"
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${token.is_current
|
||||
? html`<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.current_token_tooltip"
|
||||
)}
|
||||
</paper-tooltip>`
|
||||
: ""}
|
||||
<mwc-button
|
||||
.token=${token}
|
||||
.disabled=${token.is_current}
|
||||
.title=${this.hass.localize(`ui.common.delete`)}
|
||||
@click=${this._deleteToken}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-settings-row>`
|
||||
)
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>`;
|
||||
}
|
||||
|
||||
private async _deleteToken(ev: Event): Promise<void> {
|
||||
const token = (ev.currentTarget as any).token;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.confirm_delete",
|
||||
"name",
|
||||
token.client_name
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.hass.callWS({
|
||||
type: "auth/delete_refresh_token",
|
||||
refresh_token_id: token.id,
|
||||
});
|
||||
fireEvent(this, "hass-refresh-tokens");
|
||||
} catch (err) {
|
||||
await showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.delete_failed"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
}
|
||||
mwc-button {
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-refresh-tokens-card": HaRefreshTokens;
|
||||
}
|
||||
}
|
@@ -15,16 +15,14 @@ declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"hass-dock-sidebar": DockSidebarParams;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"hass-default-panel": DefaultPanelParams;
|
||||
"hass-edit-sidebar": undefined;
|
||||
}
|
||||
// for add event listener
|
||||
interface HTMLElementEventMap {
|
||||
"hass-dock-sidebar": HASSDomEvent<DockSidebarParams>;
|
||||
}
|
||||
interface HTMLElementEventMap {
|
||||
"hass-default-panel": HASSDomEvent<DefaultPanelParams>;
|
||||
"hass-edit-sidebar": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -278,7 +278,8 @@
|
||||
"successfully_saved": "Successfully saved",
|
||||
"successfully_deleted": "Successfully deleted",
|
||||
"back": "Back",
|
||||
"error_required": "Required"
|
||||
"error_required": "Required",
|
||||
"copied": "Copied"
|
||||
},
|
||||
"components": {
|
||||
"logbook": {
|
||||
@@ -373,12 +374,27 @@
|
||||
"video_not_supported": "Your browser does not support the video element.",
|
||||
"media_not_supported": "The Browser Media Player does not support this type of media",
|
||||
"media_browsing_error": "Media Browsing Error",
|
||||
"content-type": {
|
||||
"server": "Server",
|
||||
"library": "Library",
|
||||
"artist": "Artist",
|
||||
"class": {
|
||||
"album": "Album",
|
||||
"playlist": "Playlist"
|
||||
"app": "App",
|
||||
"artist": "Artist",
|
||||
"channel": "Channel",
|
||||
"composer": "Composer",
|
||||
"contributing_artist": "Contributing Artist",
|
||||
"directory": "Library",
|
||||
"episode": "Episode",
|
||||
"game": "Game",
|
||||
"genre": "Genre",
|
||||
"image": "Image",
|
||||
"movie": "Movie",
|
||||
"music": "Music",
|
||||
"playlist": "Playlist",
|
||||
"podcast": "Podcast",
|
||||
"season": "Season",
|
||||
"track": "Track",
|
||||
"tv_show": "TV Show",
|
||||
"url": "Url",
|
||||
"video": "Video"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -585,7 +601,8 @@
|
||||
},
|
||||
"sidebar": {
|
||||
"external_app_configuration": "App Configuration",
|
||||
"sidebar_toggle": "Sidebar Toggle"
|
||||
"sidebar_toggle": "Sidebar Toggle",
|
||||
"done": "Done"
|
||||
},
|
||||
"panel": {
|
||||
"calendar": {
|
||||
@@ -2431,6 +2448,11 @@
|
||||
"header": "Always hide the sidebar",
|
||||
"description": "This will hide the sidebar by default, similar to the mobile experience."
|
||||
},
|
||||
"customize_sidebar": {
|
||||
"header": "Change the order and hide items from the sidebar",
|
||||
"description": "You can also press and hold the header of the sidebar to activate edit mode.",
|
||||
"button": "Edit"
|
||||
},
|
||||
"vibrate": {
|
||||
"header": "Vibrate",
|
||||
"description": "Enable or disable vibration on this device when controlling devices."
|
||||
@@ -2512,14 +2534,13 @@
|
||||
"header": "Long-Lived Access Tokens",
|
||||
"description": "Create long-lived access tokens to allow your scripts to interact with your Home Assistant instance. Each token will be valid for 10 years from creation. The following long-lived access tokens are currently active.",
|
||||
"learn_auth_requests": "Learn how to make authenticated requests.",
|
||||
"created_at": "Created at {date}",
|
||||
"last_used": "Last used at {date} from {location}",
|
||||
"not_used": "Has never been used",
|
||||
"created": "Created {date}",
|
||||
"confirm_delete": "Are you sure you want to delete the access token for {name}?",
|
||||
"delete_failed": "Failed to delete the access token.",
|
||||
"create": "Create Token",
|
||||
"create_failed": "Failed to create the access token.",
|
||||
"prompt_name": "Name?",
|
||||
"name": "Name",
|
||||
"prompt_name": "Give the token a name",
|
||||
"prompt_copy_token": "Copy your access token. It will not be shown again.",
|
||||
"empty_state": "You have no long-lived access tokens yet."
|
||||
}
|
||||
@@ -2730,7 +2751,12 @@
|
||||
"reset": "Reset to demo template",
|
||||
"jinja_documentation": "Jinja2 template documentation",
|
||||
"template_extensions": "Home Assistant template extensions",
|
||||
"unknown_error_template": "Unknown error rendering template"
|
||||
"unknown_error_template": "Unknown error rendering template",
|
||||
"all_listeners": "This template listens for all state changed events.",
|
||||
"no_listeners": "This template does not listen for any state changed events and will not update automatically.",
|
||||
"listeners": "This template listens for the following state changed events:",
|
||||
"entity": "Entity",
|
||||
"domain": "Domain"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { assert } from "chai";
|
||||
import { computeStateDisplay } from "../../../src/common/entity/compute_state_display";
|
||||
import { UNKNOWN } from "../../../src/data/entity";
|
||||
|
||||
describe("computeStateDisplay", () => {
|
||||
// Mock Localize function for testing
|
||||
@@ -72,7 +73,7 @@ describe("computeStateDisplay", () => {
|
||||
};
|
||||
const stateObj: any = {
|
||||
entity_id: "sensor.test",
|
||||
state: "unknown",
|
||||
state: UNKNOWN,
|
||||
attributes: {
|
||||
unit_of_measurement: "m",
|
||||
},
|
||||
|
@@ -1469,34 +1469,12 @@
|
||||
"next": "التالى",
|
||||
"providers": {
|
||||
"command_line": {
|
||||
"abort": {
|
||||
"login_expired": ""
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": ""
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"password": "كلمة السر",
|
||||
"username": "اسم المستخدم"
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"legacy_api_password": {
|
||||
"step": {
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -19,6 +19,7 @@
|
||||
"logbook": "Дневник",
|
||||
"mailbox": "Пощенска кутия",
|
||||
"map": "Карта",
|
||||
"media_browser": "Медиен браузър",
|
||||
"profile": "Профил",
|
||||
"shopping_list": "Списък за пазаруване",
|
||||
"states": "Състояние"
|
||||
@@ -455,6 +456,7 @@
|
||||
},
|
||||
"common": {
|
||||
"cancel": "Отмени",
|
||||
"continue": "Продължи",
|
||||
"delete": "Изтриване",
|
||||
"loading": "Зареждане",
|
||||
"save": "Запазване",
|
||||
@@ -466,6 +468,10 @@
|
||||
"device": "Устройство"
|
||||
},
|
||||
"entity": {
|
||||
"entity-attribute-picker": {
|
||||
"attribute": "Атрибут",
|
||||
"show_attributes": "Показване на атрибутите"
|
||||
},
|
||||
"entity-picker": {
|
||||
"entity": "Обект"
|
||||
}
|
||||
@@ -474,6 +480,18 @@
|
||||
"loading_history": "Зареждане на история за състоянието...",
|
||||
"no_history_found": "Не е намерена история за състоянието"
|
||||
},
|
||||
"logbook": {
|
||||
"entries_not_found": "Липсват логове в дневника"
|
||||
},
|
||||
"media-browser": {
|
||||
"audio_not_supported": "Браузърът не поддържа този аудио елемент.",
|
||||
"choose_player": "Изберете плейър",
|
||||
"media_browsing_error": "Грешка в медийния браузър",
|
||||
"media_not_supported": "Плейърът на медийния браузър не поддържа този тип медия",
|
||||
"media_player": "Медиен плейър",
|
||||
"video_not_supported": "Браузърът не поддържа този видео елемент.",
|
||||
"web-browser": "Уеб браузър"
|
||||
},
|
||||
"relative_time": {
|
||||
"duration": {
|
||||
"day": "{count}{count, plural,\n one {ден}\n other {дни}\n}",
|
||||
@@ -496,6 +514,9 @@
|
||||
"enable_new_entities_label": "Активирай новодобавените устройства.",
|
||||
"title": "Системни опции за {integration}"
|
||||
},
|
||||
"domain_toggler": {
|
||||
"reset_entities": "Нулиране на обекти"
|
||||
},
|
||||
"entity_registry": {
|
||||
"editor": {
|
||||
"delete": "Изтриване",
|
||||
@@ -515,6 +536,9 @@
|
||||
"yaml_not_editable": "Настройките на този обект не могат да бъдат редактирани. Могат да се конфигурират само обектите, създадени от потребителския интерфейс."
|
||||
},
|
||||
"more_info_control": {
|
||||
"controls": "Контроли",
|
||||
"details": "Детайли",
|
||||
"history": "История",
|
||||
"script": {
|
||||
"last_action": "Последно задействане"
|
||||
},
|
||||
@@ -644,7 +668,13 @@
|
||||
"label": "Извикване на услуга",
|
||||
"service_data": "Данни за услугата"
|
||||
},
|
||||
"wait_for_trigger": {
|
||||
"continue_timeout": "Продължи след изчакване",
|
||||
"label": "Изчакайте спусък/тригър",
|
||||
"timeout": "Време на изчакване (опция)"
|
||||
},
|
||||
"wait_template": {
|
||||
"continue_timeout": "Продължи след изчакване",
|
||||
"label": "Изчакване",
|
||||
"timeout": "Изчакване (по избор)",
|
||||
"wait_template": "Шаблон за изчакване"
|
||||
@@ -692,7 +722,9 @@
|
||||
"time": {
|
||||
"after": "След",
|
||||
"before": "Преди",
|
||||
"label": "Време"
|
||||
"label": "Време",
|
||||
"type_input": "Стойност на помощника за дата/час",
|
||||
"type_value": "Фиксирано време"
|
||||
},
|
||||
"zone": {
|
||||
"entity": "Обект с местоположение",
|
||||
@@ -751,6 +783,7 @@
|
||||
"value_template": "Шаблон за стойност (незадължително)"
|
||||
},
|
||||
"state": {
|
||||
"attribute": "Атрибут (опция)",
|
||||
"for": "За период от",
|
||||
"from": "От",
|
||||
"label": "Състояние",
|
||||
@@ -775,7 +808,9 @@
|
||||
},
|
||||
"time": {
|
||||
"at": "В",
|
||||
"label": "Време"
|
||||
"label": "Време",
|
||||
"type_input": "Стойност на помощника за дата/час",
|
||||
"type_value": "Фиксирано време"
|
||||
},
|
||||
"webhook": {
|
||||
"label": "Webhook",
|
||||
@@ -796,6 +831,8 @@
|
||||
},
|
||||
"picker": {
|
||||
"add_automation": "Добавяне на автоматизация",
|
||||
"duplicate": "Дублиране",
|
||||
"duplicate_automation": "Дублиране на автоматизация",
|
||||
"header": "Редактор на автоматизации",
|
||||
"headers": {
|
||||
"name": "Име"
|
||||
@@ -815,10 +852,26 @@
|
||||
"config_documentation": "Документация за конфигурацията"
|
||||
}
|
||||
},
|
||||
"alexa": {
|
||||
"dont_expose_entity": "Не излагай обекта",
|
||||
"expose_entity": "Изложи обекта",
|
||||
"exposed": "{избран} изложен",
|
||||
"follow_domain": "следван домейн",
|
||||
"manage_domains": "Управление на домейни",
|
||||
"not_exposed": "{избран} неизложен"
|
||||
},
|
||||
"caption": "Home Assistant Cloud",
|
||||
"description_features": "Контролирайте дома си, и когато не сте вкъщи, активирайте интегрирациите с Alexa и Google Assistant.",
|
||||
"description_login": "Влезли сте като {email}",
|
||||
"description_not_login": "Не сте влезли"
|
||||
"description_not_login": "Не сте влезли",
|
||||
"google": {
|
||||
"dont_expose_entity": "Не излагай обекта",
|
||||
"expose_entity": "Изложи обекта",
|
||||
"exposed": "{избран} изложен",
|
||||
"follow_domain": "следван домейн",
|
||||
"manage_domains": "Управление на домейни",
|
||||
"not_exposed": "{избран} неизложен"
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"caption": "Общи",
|
||||
@@ -882,6 +935,7 @@
|
||||
},
|
||||
"integrations": {
|
||||
"add_integration": "Добавяне на интеграция",
|
||||
"attention": "Задълителна проверка",
|
||||
"caption": "Интеграции",
|
||||
"config_entry": {
|
||||
"delete": "Изтриване",
|
||||
@@ -897,6 +951,7 @@
|
||||
"options": "Настройки",
|
||||
"rename": "Преименуване",
|
||||
"restart_confirm": "Рестартирайте Home Assistant за да завършите премахването на интеграцията",
|
||||
"services": "{брой} {число, множествено число,\n една {услуга}\n други {услуги}\n}",
|
||||
"system_options": "Системни настройки"
|
||||
},
|
||||
"config_flow": {
|
||||
@@ -915,6 +970,7 @@
|
||||
"none": "Нищо не е конфигурирано към момента",
|
||||
"none_found": "Не са намерени интеграции",
|
||||
"none_found_detail": "Коригирайте критериите си за търсене.",
|
||||
"reconfigure": "Преконфигурирай",
|
||||
"rename_dialog": "Редактирайте името на този запис в конфигурацията"
|
||||
},
|
||||
"introduction": "Тук е възможно да конфигурирате Вашите компоненти и Home Assistant. Не всичко е възможно да се конфигурира от Интерфейса, но работим по върпоса.",
|
||||
@@ -942,6 +998,23 @@
|
||||
"mqtt": {
|
||||
"title": "MQTT"
|
||||
},
|
||||
"ozw": {
|
||||
"node": {
|
||||
"button": "Детайли за възела",
|
||||
"not_found": "Възел не е намерен"
|
||||
},
|
||||
"nodes_table": {
|
||||
"failed": "Неуспешно",
|
||||
"id": "ID",
|
||||
"manufacturer": "Производител",
|
||||
"model": "Модел",
|
||||
"query_stage": "Етап на заявка",
|
||||
"zwave_plus": "Z-Wave плюс"
|
||||
},
|
||||
"refresh_node": {
|
||||
"button": "Обнови възела"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
"caption": "Хора",
|
||||
"description": "Управлявайте хората, които следите от Home Assistant.",
|
||||
@@ -979,8 +1052,13 @@
|
||||
"group": "Презареждане на гурпите",
|
||||
"heading": "Презареждане на YAML конфигурацията",
|
||||
"introduction": "Някои части от Home Assistant могат да се презаредят без да е необходимо рестартиране. Натискането на Презареди ще отхвърли настоящата конфигурация и ще зареди новата конфигурация.",
|
||||
"mqtt": "Презареди mqtt обектите",
|
||||
"reload": "Презарежди {домейн}",
|
||||
"rpi_gpio": "Презареди GPIO обектите на Raspberry Pi ",
|
||||
"scene": "Презареди сцените",
|
||||
"script": "Презареждане на скриптовете"
|
||||
"script": "Презареждане на скриптовете",
|
||||
"smtp": "Презареди smtp услугите за уведомяване",
|
||||
"telegram": "Презареди telegram услугите за уведомяване"
|
||||
},
|
||||
"server_management": {
|
||||
"confirm_restart": "Сигурни ли сте, че искате да рестартирате Home Assistant?",
|
||||
@@ -1005,6 +1083,8 @@
|
||||
"create": "Създаване",
|
||||
"name": "Име",
|
||||
"password": "Парола",
|
||||
"password_confirm": "Потвърди парола",
|
||||
"password_not_match": "Паролите не съвпадат",
|
||||
"username": "Потребителско име"
|
||||
},
|
||||
"caption": "Потребители",
|
||||
@@ -1017,6 +1097,8 @@
|
||||
"deactivate_user": "Деактивиране на потребителя",
|
||||
"delete_user": "Изтриване на потребител",
|
||||
"name": "Име",
|
||||
"new_password": "Нова парола",
|
||||
"password_changed": "Паролата е променена!",
|
||||
"system_generated_users_not_editable": "Неуспешно обновяване на системно генерираните потребители"
|
||||
},
|
||||
"picker": {
|
||||
@@ -1102,6 +1184,11 @@
|
||||
"title": "Състояния"
|
||||
},
|
||||
"templates": {
|
||||
"all_listeners": "Този шаблон следи за всички събития на промяна на състояние.",
|
||||
"domain": "Домейн",
|
||||
"entity": "Обект",
|
||||
"listeners": "Този шаблон следи за събития със следните промени на състояния:",
|
||||
"no_listeners": "Този шаблон не следи за събития на промяна на състояние и не се актуализира автоматично.",
|
||||
"title": "Шаблон"
|
||||
}
|
||||
}
|
||||
@@ -1144,6 +1231,9 @@
|
||||
"entities": {
|
||||
"toggle": "Превключване на обекти."
|
||||
},
|
||||
"generic": {
|
||||
"state_color": "Да се оцветят ли иконите спрямо състоянието?"
|
||||
},
|
||||
"iframe": {
|
||||
"name": "Уеб страница"
|
||||
},
|
||||
@@ -1155,6 +1245,10 @@
|
||||
}
|
||||
},
|
||||
"cardpicker": {
|
||||
"by_card": "По карта",
|
||||
"by_entity": "По обект",
|
||||
"domain": "Домейн",
|
||||
"entity": "Обект",
|
||||
"no_description": "Няма налично описание."
|
||||
},
|
||||
"edit_card": {
|
||||
@@ -1166,6 +1260,7 @@
|
||||
"move": "Преместване",
|
||||
"options": "Още опции",
|
||||
"pick_card": "Изберете картата, която искате да добавите.",
|
||||
"search_cards": "Търсене на карти",
|
||||
"toggle_editor": "Превключете редактора"
|
||||
},
|
||||
"edit_lovelace": {
|
||||
@@ -1419,6 +1514,11 @@
|
||||
"submit": "Промяна"
|
||||
},
|
||||
"current_user": "В момента сте влезли като {fullName}.",
|
||||
"customize_sidebar": {
|
||||
"button": "Редактиране",
|
||||
"description": "Може също да се задържи водача на страничната лента, за да се активира режим за редактиране.",
|
||||
"header": "Промяна на реда и скриване на елементи от страничната лента"
|
||||
},
|
||||
"dashboard": {
|
||||
"dropdown_label": "Табло",
|
||||
"header": "Табло"
|
||||
@@ -1438,6 +1538,7 @@
|
||||
"confirm_delete": "Сигурни ли сте, че искате да изтриете кода за достъп за {name}?",
|
||||
"create": "Създай код",
|
||||
"create_failed": "Възникна грешка при създаването на код за достъп.",
|
||||
"created": "Създаване {дата}",
|
||||
"created_at": "Създаден на {date}",
|
||||
"delete_failed": "Възникна грешка при изтриването на кода за достъп.",
|
||||
"description": "Създайте дългосрочни кодове за достъп за да могат скриптовете Ви да взаимодействат с Home Assistant. Всеки код е валиден за 10 години от създаването си. Следните дългосрочни кодове са активни в момента.",
|
||||
@@ -1445,6 +1546,7 @@
|
||||
"header": "Дългосрочни кодове за достъп",
|
||||
"last_used": "Последно използван на {date} от {location}",
|
||||
"learn_auth_requests": "Научете се как да правите оторизирани заявки.",
|
||||
"name": "Име",
|
||||
"not_used": "Никога не е бил използван",
|
||||
"prompt_copy_token": "Копирайте кода си за достъп. Той няма да бъде показан отново.",
|
||||
"prompt_name": "Име?"
|
||||
@@ -1495,6 +1597,7 @@
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"done": "Готово",
|
||||
"external_app_configuration": "Конфигурация на приложение"
|
||||
}
|
||||
}
|
||||
|
@@ -36,8 +36,7 @@
|
||||
"unknown": "Nepoznat"
|
||||
},
|
||||
"device_tracker": {
|
||||
"home": "Kod kuće",
|
||||
"not_home": ""
|
||||
"home": "Kod kuće"
|
||||
},
|
||||
"person": {
|
||||
"home": "Kod kuće"
|
||||
@@ -94,7 +93,6 @@
|
||||
"on": "Otvoren"
|
||||
},
|
||||
"presence": {
|
||||
"off": "",
|
||||
"on": "Kod kuće"
|
||||
},
|
||||
"problem": {
|
||||
@@ -161,7 +159,6 @@
|
||||
"closing": "Zatvoreno",
|
||||
"home": "Kod kuće",
|
||||
"locked": "Zaključan",
|
||||
"not_home": "",
|
||||
"off": "Isključen",
|
||||
"ok": "OK",
|
||||
"on": "Uključen",
|
||||
@@ -245,13 +242,6 @@
|
||||
"config": {
|
||||
"automation": {
|
||||
"editor": {
|
||||
"conditions": {
|
||||
"type": {
|
||||
"zone": {
|
||||
"entity": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"type": {
|
||||
"mqtt": {
|
||||
@@ -303,45 +293,6 @@
|
||||
"empty": "Nemate poruke",
|
||||
"playback_title": "Poruku preslušati"
|
||||
},
|
||||
"page-authorize": {
|
||||
"form": {
|
||||
"providers": {
|
||||
"command_line": {
|
||||
"abort": {
|
||||
"login_expired": ""
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "",
|
||||
"invalid_code": ""
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"password": "",
|
||||
"username": ""
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"legacy_api_password": {
|
||||
"step": {
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shopping-list": {
|
||||
"add_item": "Dodajte objekat",
|
||||
"clear_completed": "Čišćenje završeno",
|
||||
|
@@ -567,10 +567,35 @@
|
||||
"loading_history": "Carregant historial d'estats...",
|
||||
"no_history_found": "No s'ha trobat cap historial d'estats."
|
||||
},
|
||||
"logbook": {
|
||||
"entries_not_found": "No s'han trobat entrades al registre."
|
||||
},
|
||||
"media-browser": {
|
||||
"audio_not_supported": "El teu navegador no és compatible amb l'element d'àudio.",
|
||||
"choose_player": "Tria el reproductor",
|
||||
"choose-source": "Tria la font",
|
||||
"class": {
|
||||
"album": "Àlbum",
|
||||
"app": "Aplicació",
|
||||
"artist": "Artista",
|
||||
"channel": "Canal",
|
||||
"composer": "Compositor",
|
||||
"contributing_artist": "Artista col·laborador",
|
||||
"directory": "Biblioteca",
|
||||
"episode": "Episodi",
|
||||
"game": "Joc",
|
||||
"genre": "Gènere",
|
||||
"image": "Imatge",
|
||||
"movie": "Pel·lícula",
|
||||
"music": "Música",
|
||||
"playlist": "Llista de reproducció",
|
||||
"podcast": "Podcast",
|
||||
"season": "Temporada",
|
||||
"track": "Pista",
|
||||
"tv_show": "Programa de TV",
|
||||
"url": "URL",
|
||||
"video": "Vídeo"
|
||||
},
|
||||
"content-type": {
|
||||
"album": "Àlbum",
|
||||
"artist": "Artista",
|
||||
@@ -703,6 +728,7 @@
|
||||
},
|
||||
"more_info_control": {
|
||||
"controls": "Controls",
|
||||
"details": "Detalls",
|
||||
"dismiss": "Desestimar el diàleg",
|
||||
"edit": "Edita entitat",
|
||||
"history": "Historial",
|
||||
@@ -1939,7 +1965,7 @@
|
||||
},
|
||||
"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",
|
||||
"no_scripts": "No s'ha pogut trobar cap script editable",
|
||||
"show_info": "Mostra informació sobre l'script",
|
||||
"trigger_script": "Dispara l'script"
|
||||
}
|
||||
@@ -2348,9 +2374,14 @@
|
||||
"title": "Estats"
|
||||
},
|
||||
"templates": {
|
||||
"all_listeners": "Aquesta plantilla escolta a tots els esdeveniments de canvi d'estat.",
|
||||
"description": "Les plantilles es renderitzen mitjançant el motor Jinja2 amb algunes extensions específiques de Home Assistant.",
|
||||
"domain": "Domini",
|
||||
"editor": "Editor de plantilles",
|
||||
"entity": "Entitat",
|
||||
"jinja_documentation": "Documentació sobre plantilles amb Jinja2",
|
||||
"listeners": "Aquesta plantilla escolta als següents esdeveniments de canvi d'estat:",
|
||||
"no_listeners": "Aquesta plantilla no escolta cap esdeveniment de canvi d'estat i no s'actualitza automàticament.",
|
||||
"reset": "Restableix a la plantilla de demostració",
|
||||
"template_extensions": "Extensions de plantilla de Home Assistant",
|
||||
"title": "Plantilla",
|
||||
@@ -2497,7 +2528,7 @@
|
||||
"no_theme": "Sense tema",
|
||||
"refresh_interval": "Interval d'actualització",
|
||||
"search": "Cerca",
|
||||
"secondary_info_attribute": "Atribut d’informació secundària",
|
||||
"secondary_info_attribute": "Atribut d'informació secundària",
|
||||
"show_icon": "Mostra icona?",
|
||||
"show_name": "Mostra nom?",
|
||||
"show_state": "Mostra estat?",
|
||||
@@ -2937,6 +2968,11 @@
|
||||
"submit": "Envia"
|
||||
},
|
||||
"current_user": "Has iniciat la sessió com a {fullName}.",
|
||||
"customize_sidebar": {
|
||||
"button": "Edita",
|
||||
"description": "També pots mantenir premuda la capçalera de la barra lateral per activar el mode d'edició.",
|
||||
"header": "Canvia l'ordre i/o amaga elements de la barra lateral"
|
||||
},
|
||||
"dashboard": {
|
||||
"description": "Tria un panell per defecte per a aquest dispositiu.",
|
||||
"dropdown_label": "Panell",
|
||||
@@ -2959,6 +2995,7 @@
|
||||
"confirm_delete": "Estàs segur que vols eliminar el token d'autenticació d'accés per {name}?",
|
||||
"create": "Crea un token d'autenticació",
|
||||
"create_failed": "No s'ha pogut crear el token d'autenticació d'accés.",
|
||||
"created": "Creat el {date}",
|
||||
"created_at": "Creat el {date}",
|
||||
"delete_failed": "No s'ha pogut eliminar el token d'autenticació d'accés.",
|
||||
"description": "Crea tokens d'autenticació d'accés de llarga durada per permetre als teus programes/scripts interactuar amb la instància de Home Assistant. Cada token és vàlid durant deu anys després de la seva creació. Els següents tokens d'autenticació d'accés de llarga durada estan actius actualment.",
|
||||
@@ -2966,9 +3003,10 @@
|
||||
"header": "Tokens d'autenticació d'accés de llarga durada",
|
||||
"last_used": "Darrer ús el {date} des de {location}",
|
||||
"learn_auth_requests": "Aprèn a fer sol·licituds autenticades.",
|
||||
"name": "Nom",
|
||||
"not_used": "Mai no s'ha utilitzat",
|
||||
"prompt_copy_token": "Copia't el token d'autenticació d'accés. No es tornarà a mostrar més endavant.",
|
||||
"prompt_name": "Nom?"
|
||||
"prompt_name": "Posa un nom al token"
|
||||
},
|
||||
"mfa_setup": {
|
||||
"close": "Tanca",
|
||||
@@ -3032,6 +3070,7 @@
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"done": "Fet",
|
||||
"external_app_configuration": "Configuració de l'aplicació",
|
||||
"sidebar_toggle": "Commutació de la barra lateral"
|
||||
}
|
||||
|
@@ -567,10 +567,35 @@
|
||||
"loading_history": "Historie stavu se načítá...",
|
||||
"no_history_found": "Historie stavu chybí."
|
||||
},
|
||||
"logbook": {
|
||||
"entries_not_found": "Nenalezeny žádné záznamy."
|
||||
},
|
||||
"media-browser": {
|
||||
"audio_not_supported": "Váš prohlížeč nepodporuje element \"audio\".",
|
||||
"choose_player": "Vyberte přehrávač",
|
||||
"choose-source": "Zvolte zdroj",
|
||||
"class": {
|
||||
"album": "Album",
|
||||
"app": "Aplikace",
|
||||
"artist": "Umělec",
|
||||
"channel": "Kanál",
|
||||
"composer": "Skladatel",
|
||||
"contributing_artist": "Přispívající umělec",
|
||||
"directory": "Knihovna",
|
||||
"episode": "Epizoda",
|
||||
"game": "Hra",
|
||||
"genre": "Žánr",
|
||||
"image": "Obrázek",
|
||||
"movie": "Film",
|
||||
"music": "Hudba",
|
||||
"playlist": "Seznam skladeb",
|
||||
"podcast": "Podcast",
|
||||
"season": "Sezóna",
|
||||
"track": "Stopa",
|
||||
"tv_show": "Televizní pořad",
|
||||
"url": "Url adresa",
|
||||
"video": "Video"
|
||||
},
|
||||
"content-type": {
|
||||
"album": "Album",
|
||||
"artist": "Umělec",
|
||||
@@ -703,6 +728,7 @@
|
||||
},
|
||||
"more_info_control": {
|
||||
"controls": "Ovládací prvky",
|
||||
"details": "Podrobnosti",
|
||||
"dismiss": "Zavřít dialog",
|
||||
"edit": "Upravit entitu",
|
||||
"history": "Historie",
|
||||
@@ -789,7 +815,7 @@
|
||||
"services": {
|
||||
"reconfigure": "Překonfigurovat zařízení ZHA (opravit zařízení). Použijte, pokud se zařízením máte problémy. Je-li dotyčné zařízení napájené bateriemi, ujistěte se prosím, že je při používání této služby spuštěné a přijímá příkazy.",
|
||||
"remove": "Odebrat zařízení ze sítě Zigbee.",
|
||||
"updateDeviceName": "Nastavte vlastní název tohoto zařízení v registru zařízení.",
|
||||
"updateDeviceName": "Nastavte vlastní název tohoto zařízení v seznamu zařízení.",
|
||||
"zigbee_information": "Zobrazit Zigbee informace zařízení."
|
||||
},
|
||||
"unknown": "Neznámý",
|
||||
@@ -1470,8 +1496,8 @@
|
||||
"name": "Název",
|
||||
"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.",
|
||||
"introduction": "Homa Assistant uchovává záznam o každé entitě, kterou kdy viděl a která může být jednoznačně identifikována. Každá z těchto entit bude mít přiděleno ID, které bude rezervováno pouze pro tuto entitu.",
|
||||
"introduction2": "Entitě můžete přepsat název, změnit její identifikátor nebo ji odebrat z Home Assistant.",
|
||||
"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?",
|
||||
@@ -1497,7 +1523,7 @@
|
||||
"header": "Konfigurace Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "Pomocníci",
|
||||
"description": "Prvky, které mohou pomoci při vytváření automatizací.",
|
||||
"description": "Správa prvků, které mohou pomoci při vytváření automatizací",
|
||||
"dialog": {
|
||||
"add_helper": "Přidat pomocníka",
|
||||
"add_platform": "Přidat {platform}",
|
||||
@@ -1628,7 +1654,7 @@
|
||||
"logs": {
|
||||
"caption": "Logy",
|
||||
"clear": "Zrušit",
|
||||
"description": "Zobrazit log Home Assistant",
|
||||
"description": "Zobrazení logů Home Assistant",
|
||||
"details": "Detaily protokolu ({level})",
|
||||
"load_full_log": "Načíst úplný protokol Home Assistanta",
|
||||
"loading_log": "Načítání protokolu chyb...",
|
||||
@@ -1946,7 +1972,7 @@
|
||||
},
|
||||
"server_control": {
|
||||
"caption": "Ovládání serveru",
|
||||
"description": "Restart a zastavení serveru Home Asistent",
|
||||
"description": "Restartování a zastavení serveru Home Asistent",
|
||||
"section": {
|
||||
"reloading": {
|
||||
"automation": "Nově načíst automatizace",
|
||||
@@ -2179,7 +2205,7 @@
|
||||
"configured_in_yaml": "Zóny konfigurované pomocí configuration.yaml nelze upravovat pomocí UI.",
|
||||
"confirm_delete": "Opravdu chcete tuto zónu smazat?",
|
||||
"create_zone": "Vytvořit zónu",
|
||||
"description": "Spravujte zóny, ve kterých chcete sledovat osoby.",
|
||||
"description": "Spravá zón, ve kterých chcete sledovat osoby",
|
||||
"detail": {
|
||||
"create": "Vytvořit",
|
||||
"delete": "Smazat",
|
||||
@@ -2348,9 +2374,14 @@
|
||||
"title": "Stavy"
|
||||
},
|
||||
"templates": {
|
||||
"all_listeners": "Tato šablona naslouchá všem změnám stavu.",
|
||||
"description": "Šablony jsou vykreslovány pomocí Jinja2 šablonového enginu s některými specifickými rozšířeními pro Home Assistant.",
|
||||
"domain": "Doména",
|
||||
"editor": "Editor šablon",
|
||||
"entity": "Entita",
|
||||
"jinja_documentation": "Dokumentace šablony Jinja2",
|
||||
"listeners": "Tato šablona naslouchá následujícím změnám stavu:",
|
||||
"no_listeners": "Tato šablona neposlouchá žádné změny stavu a nebude se automaticky aktualizovat.",
|
||||
"reset": "Obnovit ukázkovou šablonu",
|
||||
"template_extensions": "Rozšíření šablony Home Assistant",
|
||||
"title": "Šablony",
|
||||
@@ -2937,6 +2968,11 @@
|
||||
"submit": "Odeslat"
|
||||
},
|
||||
"current_user": "Nyní jste přihlášeni jako {fullName}.",
|
||||
"customize_sidebar": {
|
||||
"button": "Upravit",
|
||||
"description": "Režim úprav můžete aktivovat také stisknutím a podržením záhlaví postranního panelu.",
|
||||
"header": "Změna pořadí a skrytí položek postranního panelu"
|
||||
},
|
||||
"dashboard": {
|
||||
"description": "Vyberte výchozí dashboard pro toto zařízení.",
|
||||
"dropdown_label": "Dashboard",
|
||||
@@ -2959,6 +2995,7 @@
|
||||
"confirm_delete": "Opravdu chcete smazat přístupový token pro {name} ?",
|
||||
"create": "Vytvořte token",
|
||||
"create_failed": "Nepodařilo se vytvořit přístupový token.",
|
||||
"created": "Vytvořeno {date}",
|
||||
"created_at": "Vytvořeno {date}",
|
||||
"delete_failed": "Nepodařilo se odstranit přístupový token.",
|
||||
"description": "Vytvořte přístupové tokeny s dlouhou životností, aby vaše skripty mohly komunikovat s instancí Home Assistant. Každý token bude platný po dobu 10 let od vytvoření. Tyto tokeny s dlouhou životností jsou v současné době aktivní.",
|
||||
@@ -2966,9 +3003,10 @@
|
||||
"header": "Tokeny s dlouhou životností",
|
||||
"last_used": "Naposledy použito {date} z {location}",
|
||||
"learn_auth_requests": "Naučte se, jak posílat ověřené požadavky.",
|
||||
"name": "Název",
|
||||
"not_used": "Nikdy nebylo použito",
|
||||
"prompt_copy_token": "Zkopírujte přístupový token. Už nikdy nebude znovu zobrazen.",
|
||||
"prompt_name": "Název?"
|
||||
"prompt_name": "Pojmenujte token"
|
||||
},
|
||||
"mfa_setup": {
|
||||
"close": "Zavřít",
|
||||
@@ -3032,6 +3070,7 @@
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"done": "Hotovo",
|
||||
"external_app_configuration": "Konfigurace aplikace",
|
||||
"sidebar_toggle": "Přepínač postranního panelu"
|
||||
}
|
||||
|
@@ -1283,18 +1283,7 @@
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Cod dilysu dwy-ffactor"
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"legacy_api_password": {
|
||||
"step": {
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -507,6 +507,7 @@
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"continue": "Continue",
|
||||
"copied": "Copied",
|
||||
"delete": "Delete",
|
||||
"error_required": "Required",
|
||||
"loading": "Loading",
|
||||
@@ -567,10 +568,35 @@
|
||||
"loading_history": "Loading state history...",
|
||||
"no_history_found": "No state history found."
|
||||
},
|
||||
"logbook": {
|
||||
"entries_not_found": "No logbook entries found."
|
||||
},
|
||||
"media-browser": {
|
||||
"audio_not_supported": "Your browser does not support the audio element.",
|
||||
"choose_player": "Choose Player",
|
||||
"choose-source": "Choose Source",
|
||||
"class": {
|
||||
"album": "Album",
|
||||
"app": "App",
|
||||
"artist": "Artist",
|
||||
"channel": "Channel",
|
||||
"composer": "Composer",
|
||||
"contributing_artist": "Contributing Artist",
|
||||
"directory": "Library",
|
||||
"episode": "Episode",
|
||||
"game": "Game",
|
||||
"genre": "Genre",
|
||||
"image": "Image",
|
||||
"movie": "Movie",
|
||||
"music": "Music",
|
||||
"playlist": "Playlist",
|
||||
"podcast": "Podcast",
|
||||
"season": "Season",
|
||||
"track": "Track",
|
||||
"tv_show": "TV Show",
|
||||
"url": "Url",
|
||||
"video": "Video"
|
||||
},
|
||||
"content-type": {
|
||||
"album": "Album",
|
||||
"artist": "Artist",
|
||||
@@ -703,6 +729,7 @@
|
||||
},
|
||||
"more_info_control": {
|
||||
"controls": "Controls",
|
||||
"details": "Details",
|
||||
"dismiss": "Dismiss dialog",
|
||||
"edit": "Edit entity",
|
||||
"history": "History",
|
||||
@@ -2348,9 +2375,14 @@
|
||||
"title": "States"
|
||||
},
|
||||
"templates": {
|
||||
"all_listeners": "This template listens for all state changed events.",
|
||||
"description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.",
|
||||
"domain": "Domain",
|
||||
"editor": "Template editor",
|
||||
"entity": "Entity",
|
||||
"jinja_documentation": "Jinja2 template documentation",
|
||||
"listeners": "This template listens for the following state changed events:",
|
||||
"no_listeners": "This template does not listen for any state changed events and will not update automatically.",
|
||||
"reset": "Reset to demo template",
|
||||
"template_extensions": "Home Assistant template extensions",
|
||||
"title": "Template",
|
||||
@@ -2937,6 +2969,11 @@
|
||||
"submit": "Submit"
|
||||
},
|
||||
"current_user": "You are currently logged in as {fullName}.",
|
||||
"customize_sidebar": {
|
||||
"button": "Edit",
|
||||
"description": "You can also press and hold the header of the sidebar to activate edit mode.",
|
||||
"header": "Change the order and hide items from the sidebar"
|
||||
},
|
||||
"dashboard": {
|
||||
"description": "Pick a default dashboard for this device.",
|
||||
"dropdown_label": "Dashboard",
|
||||
@@ -2959,6 +2996,7 @@
|
||||
"confirm_delete": "Are you sure you want to delete the access token for {name}?",
|
||||
"create": "Create Token",
|
||||
"create_failed": "Failed to create the access token.",
|
||||
"created": "Created {date}",
|
||||
"created_at": "Created at {date}",
|
||||
"delete_failed": "Failed to delete the access token.",
|
||||
"description": "Create long-lived access tokens to allow your scripts to interact with your Home Assistant instance. Each token will be valid for 10 years from creation. The following long-lived access tokens are currently active.",
|
||||
@@ -2966,9 +3004,10 @@
|
||||
"header": "Long-Lived Access Tokens",
|
||||
"last_used": "Last used at {date} from {location}",
|
||||
"learn_auth_requests": "Learn how to make authenticated requests.",
|
||||
"name": "Name",
|
||||
"not_used": "Has never been used",
|
||||
"prompt_copy_token": "Copy your access token. It will not be shown again.",
|
||||
"prompt_name": "Name?"
|
||||
"prompt_name": "Give the token a name"
|
||||
},
|
||||
"mfa_setup": {
|
||||
"close": "Close",
|
||||
@@ -3032,6 +3071,7 @@
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"done": "Done",
|
||||
"external_app_configuration": "App Configuration",
|
||||
"sidebar_toggle": "Sidebar Toggle"
|
||||
}
|
||||
|
@@ -1,49 +1,4 @@
|
||||
{
|
||||
"state_badge": {
|
||||
"device_tracker": {
|
||||
"home": ""
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"automation": {
|
||||
"off": ""
|
||||
},
|
||||
"binary_sensor": {
|
||||
"default": {
|
||||
"on": ""
|
||||
},
|
||||
"presence": {
|
||||
"on": ""
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"on": ""
|
||||
},
|
||||
"group": {
|
||||
"home": "",
|
||||
"off": "",
|
||||
"on": ""
|
||||
},
|
||||
"input_boolean": {
|
||||
"on": ""
|
||||
},
|
||||
"light": {
|
||||
"off": "",
|
||||
"on": ""
|
||||
},
|
||||
"media_player": {
|
||||
"off": ""
|
||||
},
|
||||
"script": {
|
||||
"off": ""
|
||||
},
|
||||
"sensor": {
|
||||
"off": ""
|
||||
},
|
||||
"switch": {
|
||||
"on": ""
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"card": {
|
||||
"climate": {
|
||||
@@ -124,12 +79,7 @@
|
||||
"edit_ui": "Redakti kun UI",
|
||||
"edit_yaml": "Redakti kiel YAML",
|
||||
"triggers": {
|
||||
"name": "Ellasilo",
|
||||
"type": {
|
||||
"mqtt": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
"name": "Ellasilo"
|
||||
}
|
||||
},
|
||||
"picker": {
|
||||
|
@@ -19,6 +19,7 @@
|
||||
"logbook": "",
|
||||
"mailbox": "",
|
||||
"map": "",
|
||||
"media_browser": "Navegador de medios",
|
||||
"profile": "Perfil",
|
||||
"shopping_list": "Lista de compras",
|
||||
"states": ""
|
||||
@@ -106,7 +107,7 @@
|
||||
},
|
||||
"automation": {
|
||||
"off": "Desactivado",
|
||||
"on": "Encendido"
|
||||
"on": "Activada"
|
||||
},
|
||||
"binary_sensor": {
|
||||
"battery": {
|
||||
@@ -205,7 +206,7 @@
|
||||
"fan_only": "Sólo ventilador",
|
||||
"heat": "Calentar",
|
||||
"heat_cool": "Calentar/Enfriar",
|
||||
"off": "Desactivar"
|
||||
"off": "Apagado"
|
||||
},
|
||||
"configurator": {
|
||||
"configure": "Configurar",
|
||||
@@ -237,7 +238,7 @@
|
||||
"home": "En Casa",
|
||||
"locked": "Cerrado",
|
||||
"not_home": "Fuera de Casa",
|
||||
"off": "Desactivado",
|
||||
"off": "Apagado",
|
||||
"ok": "OK",
|
||||
"on": "Encendido",
|
||||
"open": "Abierto",
|
||||
@@ -419,9 +420,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 a hablar"
|
||||
"text_to_speak": "Texto a hablar",
|
||||
"turn_off": "Apagar",
|
||||
"turn_on": "Encender"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Descartar"
|
||||
@@ -498,6 +506,7 @@
|
||||
"back": "Atrás",
|
||||
"cancel": "Cancelar",
|
||||
"close": "Cerrar",
|
||||
"continue": "Continuar",
|
||||
"delete": "Eliminar",
|
||||
"error_required": "Requerido",
|
||||
"loading": "Cargando",
|
||||
@@ -544,6 +553,10 @@
|
||||
"toggle": "Alternar"
|
||||
},
|
||||
"entity": {
|
||||
"entity-attribute-picker": {
|
||||
"attribute": "Atributo",
|
||||
"show_attributes": "Mostrar atributos"
|
||||
},
|
||||
"entity-picker": {
|
||||
"clear": "Limpiar",
|
||||
"entity": "Entidad",
|
||||
@@ -554,6 +567,58 @@
|
||||
"loading_history": "Cargando historial de estado...",
|
||||
"no_history_found": "No se encontró historial de estado."
|
||||
},
|
||||
"logbook": {
|
||||
"entries_not_found": "No se encontraron entradas en el libro de registro."
|
||||
},
|
||||
"media-browser": {
|
||||
"audio_not_supported": "Su navegador no soporta el elemento de audio.",
|
||||
"choose_player": "Elige el reproductor",
|
||||
"choose-source": "Elige la fuente",
|
||||
"class": {
|
||||
"album": "Álbum",
|
||||
"app": "Aplicación",
|
||||
"artist": "Artista",
|
||||
"channel": "Canal",
|
||||
"composer": "Compositor",
|
||||
"contributing_artist": "Artista colaborador",
|
||||
"directory": "Biblioteca",
|
||||
"episode": "Episodio",
|
||||
"game": "Juego",
|
||||
"genre": "Género",
|
||||
"image": "Imagen",
|
||||
"movie": "Película",
|
||||
"music": "Música",
|
||||
"playlist": "Lista de reproducción",
|
||||
"podcast": "Podcast",
|
||||
"season": "Temporada",
|
||||
"track": "Pista",
|
||||
"tv_show": "Programa de TV",
|
||||
"url": "URL",
|
||||
"video": "Video"
|
||||
},
|
||||
"content-type": {
|
||||
"album": "Álbum",
|
||||
"artist": "Artista",
|
||||
"library": "Biblioteca",
|
||||
"playlist": "Lista de reproducción",
|
||||
"server": "Servidor"
|
||||
},
|
||||
"media_browsing_error": "Error de navegación de medios",
|
||||
"media_not_supported": "El Reproductor multimedia no es compatible con este tipo de medios",
|
||||
"media_player": "Reproductor multimedia",
|
||||
"media-player-browser": "Navegador del reproductor multimedia",
|
||||
"no_items": "No hay elementos",
|
||||
"pick": "Elegir",
|
||||
"pick-media": "Elija medios",
|
||||
"play": "Reproducir",
|
||||
"play-media": "Reproducir medios",
|
||||
"video_not_supported": "Su navegador no soporta el elemento de vídeo.",
|
||||
"web-browser": "Navegador web"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Imagen",
|
||||
"unsupported_format": "Formato no admitido, elija una imagen JPEG, PNG o GIF."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Área",
|
||||
"automation": "Parte de las siguientes automatizaciones",
|
||||
@@ -574,6 +639,7 @@
|
||||
"week": "{count} {count, plural,\n one {semana}\n other {semanas}\n}"
|
||||
},
|
||||
"future": "en {time}",
|
||||
"just_now": "Ahora mismo",
|
||||
"never": "Nunca",
|
||||
"past": "Hace {time}"
|
||||
},
|
||||
@@ -652,13 +718,19 @@
|
||||
"pattern": "Patrón de expresiones regulares para la validación del lado del cliente",
|
||||
"text": "Texto"
|
||||
},
|
||||
"platform_not_loaded": "La integración {platform} no está cargada. Agregue su configuración agregando 'default_config:' o ''{platform}:''.",
|
||||
"platform_not_loaded": "La integración {platform} no está cargada. Añádela a su archivo de configuración agregando 'default_config:' o ''{platform}:''.",
|
||||
"required_error_msg": "Este campo es requerido",
|
||||
"yaml_not_editable": "La configuración de esta entidad no se puede editar desde la interfaz de usuario. Solo las entidades configuradas desde la interfaz de usuario se pueden configurar desde la interfaz de usuario."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Cortar"
|
||||
},
|
||||
"more_info_control": {
|
||||
"controls": "Controles",
|
||||
"details": "Detalles",
|
||||
"dismiss": "Descartar diálogo",
|
||||
"edit": "Editar entidad",
|
||||
"history": "Historial",
|
||||
"person": {
|
||||
"create_zone": "Crear zona desde ubicación actual"
|
||||
},
|
||||
@@ -724,11 +796,11 @@
|
||||
},
|
||||
"zha_device_info": {
|
||||
"buttons": {
|
||||
"add": "Agregar dispositivos",
|
||||
"add": "Agregar dispositivos usando este dispositivo",
|
||||
"clusters": "Administrar clústeres",
|
||||
"reconfigure": "Reconfigurar dispositivo",
|
||||
"remove": "Eliminar dispositivo",
|
||||
"zigbee_information": "Información de Zigbee"
|
||||
"zigbee_information": "Firma del dispositivo Zigbee"
|
||||
},
|
||||
"confirmations": {
|
||||
"remove": "¿Está seguro de que desea eliminar el dispositivo?"
|
||||
@@ -748,7 +820,7 @@
|
||||
"unknown": "Desconocido",
|
||||
"zha_device_card": {
|
||||
"area_picker_label": "Área",
|
||||
"device_name_placeholder": "Nombre de usuario",
|
||||
"device_name_placeholder": "Cambiar el nombre del dispositivo",
|
||||
"update_name_button": "Actualizar Nombre"
|
||||
}
|
||||
}
|
||||
@@ -798,7 +870,7 @@
|
||||
"confirmation_text": "Todos los dispositivos en esta área quedarán sin asignar.",
|
||||
"confirmation_title": "¿Está seguro de que desea eliminar esta área?"
|
||||
},
|
||||
"description": "Visión general de todas las áreas de su casa.",
|
||||
"description": "Gestione las áreas de su casa.",
|
||||
"editor": {
|
||||
"area_id": "Identificador del área",
|
||||
"create": "Crear",
|
||||
@@ -859,13 +931,19 @@
|
||||
"label": "Llamar servico",
|
||||
"service_data": "Datos"
|
||||
},
|
||||
"wait_for_trigger": {
|
||||
"continue_timeout": "Continuar cuando el tiempo venza",
|
||||
"label": "Esperar por un desencadenador",
|
||||
"timeout": "Tiempo limite (opcional)"
|
||||
},
|
||||
"wait_template": {
|
||||
"continue_timeout": "Continuar cuando el tiempo venza",
|
||||
"label": "Esperar",
|
||||
"timeout": "Tiempo de espera (opcional)",
|
||||
"wait_template": "Plantilla de espera"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Acción no soportada: {action}"
|
||||
"unsupported_action": "No hay soporte en la interfaz de usuario para la acción: {action}"
|
||||
},
|
||||
"alias": "Nombre",
|
||||
"conditions": {
|
||||
@@ -923,7 +1001,9 @@
|
||||
"time": {
|
||||
"after": "Después de",
|
||||
"before": "Antes de",
|
||||
"label": "Hora"
|
||||
"label": "Hora",
|
||||
"type_input": "Valor de un auxiliar de tipo fecha/tiempo",
|
||||
"type_value": "Tiempo corregido"
|
||||
},
|
||||
"zone": {
|
||||
"entity": "Entidad con ubicación",
|
||||
@@ -931,7 +1011,7 @@
|
||||
"zone": "Zona"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Condición no soportada: {condition}"
|
||||
"unsupported_condition": "No hay soporte en la interfaz de usuario para la condición: {condition}"
|
||||
},
|
||||
"default_name": "Nueva Automatización",
|
||||
"description": {
|
||||
@@ -1000,7 +1080,7 @@
|
||||
"start": "Inicio"
|
||||
},
|
||||
"mqtt": {
|
||||
"label": "",
|
||||
"label": "MQTT",
|
||||
"payload": "Payload (opcional)",
|
||||
"topic": "Topic"
|
||||
},
|
||||
@@ -1011,6 +1091,7 @@
|
||||
"value_template": "Plantilla de valor (opcional)"
|
||||
},
|
||||
"state": {
|
||||
"attribute": "Atributo (opcional)",
|
||||
"for": "Por",
|
||||
"from": "De",
|
||||
"label": "Estado",
|
||||
@@ -1023,6 +1104,9 @@
|
||||
"sunrise": "Salida del sol",
|
||||
"sunset": "Puesta de sol"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Etiqueta"
|
||||
},
|
||||
"template": {
|
||||
"label": "Plantilla",
|
||||
"value_template": "Plantilla de valor"
|
||||
@@ -1034,8 +1118,10 @@
|
||||
"seconds": "Segundos"
|
||||
},
|
||||
"time": {
|
||||
"at": "A",
|
||||
"label": "Hora"
|
||||
"at": "A las",
|
||||
"label": "Hora",
|
||||
"type_input": "Valor de un auxiliar de tipo fecha/tiempo",
|
||||
"type_value": "Tiempo corregido"
|
||||
},
|
||||
"webhook": {
|
||||
"label": "Webhook",
|
||||
@@ -1050,7 +1136,7 @@
|
||||
"zone": "Zona"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Plataforma no soportada: {platform}"
|
||||
"unsupported_platform": "No hay soporte en la interfaz de usuario para la plataforma: {platform}"
|
||||
},
|
||||
"unsaved_confirm": "Tiene cambios sin guardar. ¿Estás seguro que quieres salir?"
|
||||
},
|
||||
@@ -1058,6 +1144,8 @@
|
||||
"add_automation": "Agregar automatización",
|
||||
"delete_automation": "Eliminar automatización",
|
||||
"delete_confirm": "¿Está seguro de que desea eliminar esta automatización?",
|
||||
"duplicate": "Duplicar",
|
||||
"duplicate_automation": "Duplicar automatización",
|
||||
"edit_automation": "Editar automatización",
|
||||
"header": "Editor de automatizaciones",
|
||||
"headers": {
|
||||
@@ -1139,8 +1227,13 @@
|
||||
},
|
||||
"alexa": {
|
||||
"banner": "La edición de las entidades expuestas a través de esta interfaz de usuario está deshabilitada porque ha configurado filtros de entidad en configuration.yaml.",
|
||||
"dont_expose_entity": "No exponer la entidad",
|
||||
"expose": "Exponer a Alexa",
|
||||
"expose_entity": "Exponer la entidad",
|
||||
"exposed": "{selected} expuesto",
|
||||
"exposed_entities": "Entidades expuestas",
|
||||
"follow_domain": "Seguir dominio",
|
||||
"not_exposed": "{selected} no expuesto",
|
||||
"not_exposed_entities": "Entidades no expuestas",
|
||||
"title": "Alexa"
|
||||
},
|
||||
@@ -1178,8 +1271,14 @@
|
||||
"google": {
|
||||
"banner": "La edición de las entidades expuestas a través de esta interfaz de usuario está deshabilitada porque ha configurado filtros de entidad en configuration.yaml.",
|
||||
"disable_2FA": "Deshabilitar la autenticación de dos factores",
|
||||
"dont_expose_entity": "No exponer la entidad",
|
||||
"expose": "Exponer al Asistente de Google",
|
||||
"expose_entity": "Exponer la entidad",
|
||||
"exposed": "{selected} expuesto",
|
||||
"exposed_entities": "Entidades expuestas",
|
||||
"follow_domain": "Seguir dominio",
|
||||
"manage_domains": "Gestionar dominios",
|
||||
"not_exposed": "{selected} no expuesto",
|
||||
"not_exposed_entities": "Entidades no expuestas",
|
||||
"sync_to_google": "Sincronizar los cambios con Google.",
|
||||
"title": "Asistente de Google"
|
||||
@@ -1281,6 +1380,7 @@
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"add_prompt": "Aún no se ha agregado ningún {name} con este dispositivo. Puede agregar uno haciendo clic en el botón + de arriba.",
|
||||
"automation": {
|
||||
"actions": {
|
||||
"caption": "Cuando algo se desencadena..."
|
||||
@@ -1300,6 +1400,7 @@
|
||||
"caption": "Dispositivos",
|
||||
"confirm_delete": "¿Está seguro de que desea eliminar este dispositivo?",
|
||||
"confirm_rename_entity_ids": "¿También desea cambiar el nombre de la identificación de la entidad de sus entidades?",
|
||||
"confirm_rename_entity_ids_warning": "Esto no cambiará ninguna configuración (como automatizaciones, scripts, escenas, Lovelace) que esté usando actualmente estas entidades, tendrás que actualizarlas tú mismo.",
|
||||
"data_table": {
|
||||
"area": "Área",
|
||||
"battery": "Batería",
|
||||
@@ -1341,7 +1442,7 @@
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entidades",
|
||||
"description": "Visión general de todas las entidades conocidas.",
|
||||
"description": "Gestione todas las entidades conocidas.",
|
||||
"picker": {
|
||||
"disable_selected": {
|
||||
"button": "Deshabilitar selección",
|
||||
@@ -1393,7 +1494,7 @@
|
||||
"header": "Configurar Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "Auxiliares",
|
||||
"description": "Elementos que pueden ayudar a construir automatizaciones.",
|
||||
"description": "Gestionar elementos ayudan a construir automatizaciones",
|
||||
"dialog": {
|
||||
"add_helper": "Agregar auxiliar",
|
||||
"add_platform": "Añadir {platform}",
|
||||
@@ -1444,6 +1545,7 @@
|
||||
},
|
||||
"integrations": {
|
||||
"add_integration": "Agregar integración",
|
||||
"attention": "Atención requerida",
|
||||
"caption": "Integraciones",
|
||||
"config_entry": {
|
||||
"area": "En {area}",
|
||||
@@ -1464,6 +1566,7 @@
|
||||
"options": "Opciones",
|
||||
"rename": "Renombrar",
|
||||
"restart_confirm": "Reinicie Home Assistant para terminar de eliminar esta integración.",
|
||||
"services": "{count} {count, plural,\n one {service}\n other {services}\n}",
|
||||
"settings_button": "Editar configuración para {integration}",
|
||||
"system_options": "Opciones de Sistema",
|
||||
"system_options_button": "Opciones del sistema para {integration}",
|
||||
@@ -1486,7 +1589,7 @@
|
||||
},
|
||||
"configure": "Configurar",
|
||||
"configured": "Configurado",
|
||||
"description": "Administrar y configurar integraciones",
|
||||
"description": "Gestione las integraciones",
|
||||
"details": "Detalles de integración",
|
||||
"discovered": "Descubierto",
|
||||
"home_assistant_website": "Sitio web de Home Assistant",
|
||||
@@ -1510,6 +1613,7 @@
|
||||
"none_found_detail": "Ajuste sus criterios de búsqueda",
|
||||
"note_about_integrations": "No todas las integraciones se pueden configurar a través de la interfaz de usuario.",
|
||||
"note_about_website_reference": "Hay más disponibles en ",
|
||||
"reconfigure": "Reconfigurar",
|
||||
"rename_dialog": "Editar el nombre de esta entrada de configuración",
|
||||
"rename_input_label": "Ingresar Nombre",
|
||||
"search": "Buscar integraciones"
|
||||
@@ -1619,13 +1723,84 @@
|
||||
"title": "",
|
||||
"topic": "tema"
|
||||
},
|
||||
"ozw": {
|
||||
"button": "Configurar",
|
||||
"common": {
|
||||
"controller": "Controlador",
|
||||
"instance": "Instancia",
|
||||
"network": "Red"
|
||||
},
|
||||
"navigation": {
|
||||
"network": "Red",
|
||||
"nodes": "Nodos",
|
||||
"select_instance": "Seleccione la instancia"
|
||||
},
|
||||
"network_status": {
|
||||
"details": {
|
||||
"driverallnodesqueried": "Todos los nodos han sido consultados",
|
||||
"driverallnodesqueriedsomedead": "Todos los nodos han sido consultados. Algunos nodos fueron encontrados inactivos.",
|
||||
"driverawakenodesqueries": "Se han consultado todos los nodos activos",
|
||||
"driverfailed": "No se pudo conectar al controlador Z-Wave",
|
||||
"driverready": "Iniciando el controlador de Z-Wave",
|
||||
"driverremoved": "El controlador ha sido eliminado",
|
||||
"driverreset": "El controlador se ha reiniciado",
|
||||
"offline": "OZWDaemon fuera de línea",
|
||||
"ready": "Listo para conectar",
|
||||
"started": "Conectado a MQTT",
|
||||
"starting": "Conectando con MQTT",
|
||||
"stopped": "OpenZWave se detuvo"
|
||||
},
|
||||
"offline": "Fuera de línea",
|
||||
"online": "En línea",
|
||||
"starting": "Comenzando",
|
||||
"unknown": "Desconocido"
|
||||
},
|
||||
"network": {
|
||||
"header": "Gestión de red",
|
||||
"introduction": "Gestione las funciones de toda la red."
|
||||
},
|
||||
"node_query_stages": {
|
||||
"complete": "El proceso de consulta está completo",
|
||||
"configuration": "Obteniendo los valores de configuración del nodo",
|
||||
"dynamic": "Obteniendo los valores que cambian con frecuencia del nodo"
|
||||
},
|
||||
"node": {
|
||||
"button": "Detalles del nodo",
|
||||
"not_found": "Nodo no encontrado"
|
||||
},
|
||||
"nodes_table": {
|
||||
"failed": "Fallido",
|
||||
"id": "ID",
|
||||
"manufacturer": "Fabricante",
|
||||
"model": "Modelo",
|
||||
"query_stage": "Etapa de consulta",
|
||||
"zwave_plus": "Z-Wave Plus"
|
||||
},
|
||||
"refresh_node": {
|
||||
"battery_note": "Si el nodo funciona con batería, asegúrese de activarlo antes de continuar.",
|
||||
"button": "Actualizar nodo",
|
||||
"complete": "Actualización de nodo completa",
|
||||
"description": "Esto le indicará a OpenZWave que vuelva a consultar un nodo y actualice las clases de comando, las capacidades y los valores del nodo.",
|
||||
"node_status": "Estado del nodo",
|
||||
"refreshing_description": "Actualizando la información del nodo ...",
|
||||
"start_refresh_button": "Iniciar actualización",
|
||||
"step": "Paso",
|
||||
"title": "Actualizar la información del nodos",
|
||||
"wakeup_header": "Instrucciones de activación para",
|
||||
"wakeup_instructions_source": "Las instrucciones de activación se obtienen de la base de datos de dispositivos de la comunidad OpenZWave."
|
||||
},
|
||||
"select_instance": {
|
||||
"header": "Seleccione una instancia OpenZWave",
|
||||
"introduction": "Tiene más de una instancia de OpenZWave en ejecución. ¿Qué instancia te gustaría gestionar?"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
"add_person": "Agregar persona",
|
||||
"caption": "Personas",
|
||||
"confirm_delete": "¿Está seguro de que desea eliminar a esta persona?",
|
||||
"confirm_delete2": "Todos los dispositivos que pertenecen a esta persona quedarán sin asignar.",
|
||||
"create_person": "Crear persona",
|
||||
"description": "Gestiona las personas que rastrea Home Assistant.",
|
||||
"description": "Gestione las personas que rastrea Home Assistant.",
|
||||
"detail": {
|
||||
"create": "Crear",
|
||||
"delete": "Eliminar",
|
||||
@@ -1648,7 +1823,7 @@
|
||||
"scene": {
|
||||
"activated": "Escena activada {name}.",
|
||||
"caption": "Escenas",
|
||||
"description": "Crear y editar escenas",
|
||||
"description": "Gestionar escenas",
|
||||
"editor": {
|
||||
"default_name": "Nueva escena",
|
||||
"devices": {
|
||||
@@ -1743,7 +1918,7 @@
|
||||
"reloading": {
|
||||
"automation": "Recargar automatizaciones",
|
||||
"core": "Recargar ubicación y personalizaciones",
|
||||
"group": "Recargar grupos",
|
||||
"group": "Recargar grupos, entidades grupales, y servicios de notificación",
|
||||
"heading": "Recarga de configuración YAML",
|
||||
"input_boolean": "Recargar controles booleanos",
|
||||
"input_datetime": "Recargar controles de fechas",
|
||||
@@ -1751,9 +1926,16 @@
|
||||
"input_select": "Recargar controles de selección",
|
||||
"input_text": "Recargar controles de texto",
|
||||
"introduction": "Algunas partes de Home Assistant pueden recargarse sin requerir un reinicio. Al presionar recargar se descargará su configuración YAML actual y se cargará la nueva.",
|
||||
"mqtt": "Recargar entidades MQTT",
|
||||
"person": "Recargar personas",
|
||||
"reload": "Recargar {domain}",
|
||||
"rest": "Recargar entidades \"rest\" y servicios de notificación.",
|
||||
"rpi_gpio": "Recargue las entidades GPIO de la Raspberry Pi",
|
||||
"scene": "Recargar escenas",
|
||||
"script": "Recargar scripts",
|
||||
"smtp": "Recargar servicios de notificación smtp",
|
||||
"telegram": "Recargar servicios de notificación de telegram",
|
||||
"template": "Recargar las entidades de la plantilla",
|
||||
"zone": "Recargar zonas"
|
||||
},
|
||||
"server_management": {
|
||||
@@ -1773,6 +1955,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "Añadir etiqueta",
|
||||
"automation_title": "La etiqueta {name} ha sido escaneada",
|
||||
"caption": "Etiquetas",
|
||||
"create_automation": "Crea automatización con etiqueta",
|
||||
"description": "Gestionar etiquetas",
|
||||
"detail": {
|
||||
"create": "Crear",
|
||||
"create_and_write": "Crear y escribir",
|
||||
"delete": "Eliminar",
|
||||
"description": "Descripción",
|
||||
"name": "Nombre",
|
||||
"new_tag": "Nueva etiqueta",
|
||||
"tag_id": "ID de etiqueta",
|
||||
"tag_id_placeholder": "Autogenerado cuando se deja vacío",
|
||||
"update": "Actualizar"
|
||||
},
|
||||
"edit": "Editar",
|
||||
"headers": {
|
||||
"last_scanned": "Último escaneado",
|
||||
"name": "Nombre"
|
||||
},
|
||||
"never_scanned": "Nunca escaneado",
|
||||
"no_tags": "Sin etiquetas",
|
||||
"write": "Escribir"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Agregar usuario",
|
||||
@@ -1809,7 +2017,7 @@
|
||||
"system": "Sistema"
|
||||
}
|
||||
},
|
||||
"users_privileges_note": "El grupo de usuarios es un trabajo en progreso. El usuario no podrá administrar la instancia a través de la interfaz de usuario. Todavía estamos auditando todos los puntos finales de la API de administración para garantizar que limiten correctamente el acceso a los administradores."
|
||||
"users_privileges_note": "El grupo de usuarios es un trabajo en progreso. El usuario no podrá administrar la instancia a través de la interfaz de usuario. Todavía estamos auditando todos los puntos finales de la API de administración para garantizar que limiten correctamente el acceso solo a los administradores."
|
||||
},
|
||||
"zha": {
|
||||
"add_device_page": {
|
||||
@@ -1847,7 +2055,7 @@
|
||||
"clusters": {
|
||||
"header": "Clústeres",
|
||||
"help_cluster_dropdown": "Seleccione un clúster para ver atributos y comandos.",
|
||||
"introduction": "Los clústeres son los bloques de construcción para la funcionalidad de Zigbee. Separa la funcionalidad en unidades lógicas. Hay tipos de clientes y servidores que se componen de atributos y comandos."
|
||||
"introduction": "Los clústeres son los bloques de construcción para la funcionalidad de Zigbee. Ellos separan la funcionalidad en unidades lógicas. Hay tipos de clientes y servidores que se componen de atributos y comandos."
|
||||
},
|
||||
"common": {
|
||||
"add_devices": "Agregar dispositivos",
|
||||
@@ -1879,7 +2087,7 @@
|
||||
"create_group": "Zigbee Home Automation - Crear grupo",
|
||||
"create_group_details": "Ingrese los detalles requeridos para crear un nuevo grupo zigbee",
|
||||
"creating_group": "Creando grupo",
|
||||
"description": "Crear y modificar grupos Zigbee",
|
||||
"description": "Gestione los grupos Zigbee",
|
||||
"group_details": "Aquí están todos los detalles para el grupo Zigbee seleccionado.",
|
||||
"group_id": "Identificación del grupo",
|
||||
"group_info": "Información del grupo",
|
||||
@@ -2091,9 +2299,14 @@
|
||||
"title": "Estados"
|
||||
},
|
||||
"templates": {
|
||||
"all_listeners": "Esta plantilla escucha todos los eventos de cambio de estado.",
|
||||
"description": "Las plantillas se representan utilizando el motor de plantillas Jinja2 con algunas extensiones específicas de Home Assistant.",
|
||||
"domain": "Dominio",
|
||||
"editor": "Editor de plantillas",
|
||||
"entity": "Entidad",
|
||||
"jinja_documentation": "Documentación de plantillas Jinja2",
|
||||
"listeners": "Esta plantilla escucha los eventos de los siguientes cambios de estado:",
|
||||
"no_listeners": "Esta plantilla no escucha ningún evento de cambio de estado y no se actualizará automáticamente.",
|
||||
"template_extensions": "Extensiones de plantilla de Home Assistant",
|
||||
"title": "Plantilla",
|
||||
"unknown_error_template": "Error desconocido al mostrar la plantilla"
|
||||
@@ -2335,7 +2548,11 @@
|
||||
}
|
||||
},
|
||||
"cardpicker": {
|
||||
"by_card": "Por Tarjeta",
|
||||
"by_entity": "Por Entidad",
|
||||
"custom_card": "Personalizado",
|
||||
"domain": "Dominio",
|
||||
"entity": "Entidad",
|
||||
"no_description": "No hay descripción disponible."
|
||||
},
|
||||
"edit_card": {
|
||||
@@ -2349,10 +2566,11 @@
|
||||
"options": "Mas opciones",
|
||||
"pick_card": "¿Qué tarjeta desea agregar?",
|
||||
"pick_card_view_title": "¿Qué tarjeta le gustaría agregar a su vista de {name} ?",
|
||||
"search_cards": "Buscar tarjetas",
|
||||
"show_code_editor": "Mostrar editor de código",
|
||||
"show_visual_editor": "Mostrar el editor visual",
|
||||
"toggle_editor": "Cambiar editor",
|
||||
"typed_header": "{tipo} Configuración de la tarjeta",
|
||||
"typed_header": "{type} Configuración de la tarjeta",
|
||||
"unsaved_changes": "Tiene cambios no guardados"
|
||||
},
|
||||
"edit_lovelace": {
|
||||
@@ -2429,7 +2647,7 @@
|
||||
},
|
||||
"menu": {
|
||||
"close": "Cerrar",
|
||||
"configure_ui": "Configurar interfaz de usuario",
|
||||
"configure_ui": "Editar interfaz de usuario",
|
||||
"exit_edit_mode": "Salir del modo de edición de la interfaz de usuario",
|
||||
"help": "Ayuda",
|
||||
"refresh": "Refrescar",
|
||||
@@ -2661,6 +2879,11 @@
|
||||
"submit": "Enviar"
|
||||
},
|
||||
"current_user": "Actualmente estás conectado como {fullName} .",
|
||||
"customize_sidebar": {
|
||||
"button": "Editar",
|
||||
"description": "También puede mantener pulsado el encabezado de la barra lateral para activar el modo de edición.",
|
||||
"header": "Cambiar el orden y ocultar elementos de la barra lateral"
|
||||
},
|
||||
"dashboard": {
|
||||
"description": "Elija un tablero predeterminado para este dispositivo.",
|
||||
"dropdown_label": "Tablero",
|
||||
@@ -2683,6 +2906,7 @@
|
||||
"confirm_delete": "¿Está seguro de que desea eliminar el token de acceso para {name}?",
|
||||
"create": "Crear Token",
|
||||
"create_failed": "No se pudo crear el token de acceso.",
|
||||
"created": "Creado {date}",
|
||||
"created_at": "Creado en {date}",
|
||||
"delete_failed": "No se pudo eliminar el token de acceso.",
|
||||
"description": "Cree tokens de acceso de larga duración para permitir que sus secuencias de comandos interactúen con la instancia de su Home Assistant. Cada token tendrá una validez de 10 años a partir de su creación. Los siguientes tokens de acceso de larga duración están activos actualmente.",
|
||||
@@ -2690,9 +2914,10 @@
|
||||
"header": "Tokens de acceso de larga duración",
|
||||
"last_used": "Utilizado por última vez en {date} desde {location}.",
|
||||
"learn_auth_requests": "Aprenda a realizar solicitudes autenticadas.",
|
||||
"name": "Nombre",
|
||||
"not_used": "Nunca se ha utilizado",
|
||||
"prompt_copy_token": "Copia tu token de acceso. No se volverá a mostrar",
|
||||
"prompt_name": "¿Nombre?"
|
||||
"prompt_name": "Dale un nombre al token"
|
||||
},
|
||||
"mfa_setup": {
|
||||
"close": "Cerrar",
|
||||
@@ -2748,6 +2973,7 @@
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"done": "Hecho",
|
||||
"external_app_configuration": "Configuración de la aplicación",
|
||||
"sidebar_toggle": "Alternar barra lateral"
|
||||
}
|
||||
|
@@ -571,6 +571,15 @@
|
||||
"audio_not_supported": "Tu navegador no es compatible con el elemento de audio.",
|
||||
"choose_player": "Elige reproductor",
|
||||
"choose-source": "Elige la fuente",
|
||||
"class": {
|
||||
"playlist": "Lista de reproducción",
|
||||
"podcast": "Podcast",
|
||||
"season": "Temporada",
|
||||
"track": "Pista",
|
||||
"tv_show": "Programa de TV",
|
||||
"url": "Url",
|
||||
"video": "Vídeo"
|
||||
},
|
||||
"content-type": {
|
||||
"album": "Álbum",
|
||||
"artist": "Artista",
|
||||
|
@@ -1689,9 +1689,6 @@
|
||||
},
|
||||
"step": {
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": "باز کردن **{mfa_module_name}** * * * در دستگاه خود را برای مشاهده شما دو فاکتور تأیید هویت کد و هویت خود را تایید کنید:"
|
||||
}
|
||||
}
|
||||
|
@@ -37,8 +37,7 @@
|
||||
"unknown": "Unbekannt"
|
||||
},
|
||||
"device_tracker": {
|
||||
"home": "Dahei",
|
||||
"not_home": ""
|
||||
"home": "Dahei"
|
||||
},
|
||||
"person": {
|
||||
"home": "Dahei"
|
||||
@@ -97,7 +96,6 @@
|
||||
"on": "Offä"
|
||||
},
|
||||
"presence": {
|
||||
"off": "",
|
||||
"on": "Dahei"
|
||||
},
|
||||
"problem": {
|
||||
@@ -419,9 +417,6 @@
|
||||
"time": {
|
||||
"after": "Nachhär",
|
||||
"before": "Vorhär"
|
||||
},
|
||||
"zone": {
|
||||
"entity": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -515,45 +510,6 @@
|
||||
"mailbox": {
|
||||
"delete_button": "Lösche"
|
||||
},
|
||||
"page-authorize": {
|
||||
"form": {
|
||||
"providers": {
|
||||
"command_line": {
|
||||
"abort": {
|
||||
"login_expired": ""
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "",
|
||||
"invalid_code": ""
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"password": "",
|
||||
"username": ""
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"legacy_api_password": {
|
||||
"step": {
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"change_password": {
|
||||
"confirm_new_password": "Nöis Passwort bestätige",
|
||||
|
@@ -19,9 +19,6 @@
|
||||
},
|
||||
"state_badge": {
|
||||
"alarm_control_panel": {
|
||||
"armed_away": "",
|
||||
"armed_custom_bypass": "",
|
||||
"armed_night": "",
|
||||
"pending": "अपूर्ण"
|
||||
},
|
||||
"default": {
|
||||
@@ -31,8 +28,7 @@
|
||||
"unknown": "अज्ञात"
|
||||
},
|
||||
"device_tracker": {
|
||||
"home": "घर",
|
||||
"not_home": ""
|
||||
"home": "घर"
|
||||
},
|
||||
"person": {
|
||||
"home": "घर"
|
||||
@@ -79,27 +75,16 @@
|
||||
"off": "विशद",
|
||||
"on": "अनुसन्धानित"
|
||||
},
|
||||
"occupancy": {
|
||||
"off": "",
|
||||
"on": ""
|
||||
},
|
||||
"opening": {
|
||||
"on": "खुला"
|
||||
},
|
||||
"presence": {
|
||||
"off": "",
|
||||
"on": "घर"
|
||||
},
|
||||
"safety": {
|
||||
"off": "सुरक्षित",
|
||||
"on": "असुरक्षित"
|
||||
},
|
||||
"smoke": {
|
||||
"off": ""
|
||||
},
|
||||
"vibration": {
|
||||
"on": ""
|
||||
},
|
||||
"window": {
|
||||
"off": "बंद",
|
||||
"on": "खुली"
|
||||
@@ -130,15 +115,10 @@
|
||||
"on": "चालू"
|
||||
},
|
||||
"group": {
|
||||
"closing": "",
|
||||
"home": "घर",
|
||||
"not_home": "",
|
||||
"off": "बंद",
|
||||
"ok": "",
|
||||
"on": "चालू",
|
||||
"open": "",
|
||||
"problem": "समस्या",
|
||||
"stopped": ""
|
||||
"problem": "समस्या"
|
||||
},
|
||||
"input_boolean": {
|
||||
"off": "बंद",
|
||||
@@ -254,9 +234,6 @@
|
||||
"time": {
|
||||
"after": "बाद",
|
||||
"before": "पहले"
|
||||
},
|
||||
"zone": {
|
||||
"entity": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -428,39 +405,6 @@
|
||||
"form": {
|
||||
"next": "अगला",
|
||||
"providers": {
|
||||
"command_line": {
|
||||
"abort": {
|
||||
"login_expired": ""
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "",
|
||||
"invalid_code": ""
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"password": "",
|
||||
"username": ""
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"legacy_api_password": {
|
||||
"step": {
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": ""
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
"step": {
|
||||
"init": {
|
||||
|
@@ -419,6 +419,8 @@
|
||||
"unlock": "Kinyit"
|
||||
},
|
||||
"media_player": {
|
||||
"media_next_track": "Következő",
|
||||
"media_previous_track": "Előző",
|
||||
"sound_mode": "Hangzás",
|
||||
"source": "Forrás",
|
||||
"text_to_speak": "Beszéd szövege"
|
||||
@@ -554,6 +556,20 @@
|
||||
"loading_history": "Állapot előzmények betöltése...",
|
||||
"no_history_found": "Nem található előzmény."
|
||||
},
|
||||
"media-browser": {
|
||||
"choose-source": "Forrás kiválasztása",
|
||||
"content-type": {
|
||||
"album": "Album",
|
||||
"artist": "Előadó",
|
||||
"library": "Könyvtár",
|
||||
"playlist": "Lejátszási lista",
|
||||
"server": "Szerver"
|
||||
},
|
||||
"no_items": "Nincsenek elemek",
|
||||
"pick-media": "Média kiválasztása",
|
||||
"play": "Lejátszás",
|
||||
"play-media": "Média lejátszása"
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Terület",
|
||||
"automation": "A következő automatizálások része",
|
||||
@@ -574,6 +590,7 @@
|
||||
"week": "{count} {count, plural,\n one {héttel}\n other {héttel}\n}"
|
||||
},
|
||||
"future": "{time} később",
|
||||
"just_now": "Éppen most",
|
||||
"never": "Soha",
|
||||
"past": "{time} ezelőtt"
|
||||
},
|
||||
@@ -656,6 +673,9 @@
|
||||
"required_error_msg": "Ez a mező kötelező",
|
||||
"yaml_not_editable": "Ennek az entitásnak a beállításai nem szerkeszthetők a felhasználói felületről. Csak a felhasználói felületről beállított entitások konfigurálhatók a felhasználói felületről."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Kivágás"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Párbeszédpanel elvetése",
|
||||
"edit": "Entitás szerkesztése",
|
||||
@@ -798,7 +818,7 @@
|
||||
"confirmation_text": "Minden ebben a területben lévő eszköz hozzárendelés nélküli lesz.",
|
||||
"confirmation_title": "Biztosan törölni szeretnéd ezt a területet?"
|
||||
},
|
||||
"description": "Az összes otthoni terület áttekintése",
|
||||
"description": "Otthoni területek kezelése",
|
||||
"editor": {
|
||||
"area_id": "Terület ID",
|
||||
"create": "Létrehozás",
|
||||
@@ -820,7 +840,7 @@
|
||||
},
|
||||
"automation": {
|
||||
"caption": "Automatizálások",
|
||||
"description": "Automatizálások létrehozása és szerkesztése",
|
||||
"description": "Automatizálások kezelése",
|
||||
"editor": {
|
||||
"actions": {
|
||||
"add": "Művelet hozzáadása",
|
||||
@@ -852,6 +872,15 @@
|
||||
"label": "Esemény meghívása",
|
||||
"service_data": "Szolgáltatás adatai"
|
||||
},
|
||||
"repeat": {
|
||||
"label": "Ismétlés",
|
||||
"type_select": "Ismétlés típusa",
|
||||
"type": {
|
||||
"count": {
|
||||
"label": "Számláló"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scene": {
|
||||
"label": "Jelenet aktiválása"
|
||||
},
|
||||
@@ -865,7 +894,7 @@
|
||||
"wait_template": "Várakozási sablon"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Nem támogatott művelet: {action}"
|
||||
"unsupported_action": "Nincs felhasználói felület támogatás a művelethez: {action}"
|
||||
},
|
||||
"alias": "Név",
|
||||
"conditions": {
|
||||
@@ -931,7 +960,7 @@
|
||||
"zone": "Zóna"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Nem támogatott feltétel: {condition}"
|
||||
"unsupported_condition": "Nincs felhasználói felület támogatás a feltételhez: {condition}"
|
||||
},
|
||||
"default_name": "Új Automatizálás",
|
||||
"description": {
|
||||
@@ -1049,7 +1078,7 @@
|
||||
"zone": "Zóna"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Nem támogatott platform: {platform}"
|
||||
"unsupported_platform": "Nincs felhasználói felület támogatás a platformhoz: {platform}"
|
||||
},
|
||||
"unsaved_confirm": "Vannak nem mentett módosítások. Biztos, hogy elhagyod az oldalt?"
|
||||
},
|
||||
@@ -1138,9 +1167,11 @@
|
||||
},
|
||||
"alexa": {
|
||||
"banner": "A feltárt entitások szerkesztése funkció le lett tiltva ezen a felületen, mert entitásszűrőket állítottál be a configuration.yaml fájlban.",
|
||||
"dont_expose_entity": "Entitás feltárásának törlése",
|
||||
"expose": "Feltárás Alexának",
|
||||
"expose_entity": "Entitás feltárása",
|
||||
"exposed_entities": "Feltárt entitások",
|
||||
"not_exposed_entities": "Nem feltárt entitások",
|
||||
"not_exposed_entities": "Feltáratlan entitások",
|
||||
"title": "Alexa"
|
||||
},
|
||||
"caption": "Home Assistant Felhő",
|
||||
@@ -1177,9 +1208,11 @@
|
||||
"google": {
|
||||
"banner": "A feltárt entitások szerkesztése funkció le lett tiltva ezen a felületen, mert entitásszűrőket állítottál be a configuration.yaml fájlban.",
|
||||
"disable_2FA": "Kétfaktoros hitelesítés letiltása",
|
||||
"dont_expose_entity": "Entitás feltárásának törlése",
|
||||
"expose": "Feltárás a Google Asszisztensnek",
|
||||
"expose_entity": "Entitás feltárása",
|
||||
"exposed_entities": "Feltárt entitások",
|
||||
"not_exposed_entities": "Nem feltárt entitások",
|
||||
"not_exposed_entities": "Feltáratlan entitások",
|
||||
"sync_to_google": "A változások szinkronizálása a Google-lal.",
|
||||
"title": "Google Asszisztens"
|
||||
},
|
||||
@@ -1340,7 +1373,7 @@
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entitások",
|
||||
"description": "Az összes ismert entitás áttekintése",
|
||||
"description": "Ismert entitások kezelése",
|
||||
"picker": {
|
||||
"disable_selected": {
|
||||
"button": "Kiválasztottak letiltása",
|
||||
@@ -1392,7 +1425,7 @@
|
||||
"header": "Home Assistant beállítása",
|
||||
"helpers": {
|
||||
"caption": "Segítők",
|
||||
"description": "Automatizálások létrehozását segítő elemek",
|
||||
"description": "Automatizálások létrehozását segítő elemek kezelése",
|
||||
"dialog": {
|
||||
"add_helper": "Segítő hozzáadása",
|
||||
"add_platform": "{platform} hozzáadása",
|
||||
@@ -1420,7 +1453,7 @@
|
||||
"built_using": "Buildelve:",
|
||||
"caption": "Infó",
|
||||
"custom_uis": "Egyéni felhasználói felületek:",
|
||||
"description": "Információ a Home Assistant telepítésedről",
|
||||
"description": "Telepítési információ megtekintése a Home Assistant-ról",
|
||||
"developed_by": "Egy csomó fantasztikus ember által kifejlesztve.",
|
||||
"documentation": "Dokumentáció",
|
||||
"frontend": "frontend-ui",
|
||||
@@ -1459,6 +1492,8 @@
|
||||
"no_device": "Eszköz nélküli entitások",
|
||||
"no_devices": "Ez az integráció nem rendelkezik eszközökkel.",
|
||||
"options": "Opciók",
|
||||
"reload": "Újratöltés",
|
||||
"reload_confirm": "Az integráció újra lett töltve",
|
||||
"rename": "Átnevezés",
|
||||
"restart_confirm": "Indítsd újra a Home Assistant-ot az integráció törlésének befejezéséhez",
|
||||
"settings_button": "{integration} beállításainak szerkesztése",
|
||||
@@ -1483,7 +1518,7 @@
|
||||
},
|
||||
"configure": "Beállítás",
|
||||
"configured": "Konfigurálva",
|
||||
"description": "Integrációk kezelése és beállítása",
|
||||
"description": "Integrációk kezelése",
|
||||
"details": "Integráció részletei",
|
||||
"discovered": "Felfedezett",
|
||||
"home_assistant_website": "Home Assistant weboldal",
|
||||
@@ -1511,7 +1546,7 @@
|
||||
"rename_input_label": "Bejegyzés neve",
|
||||
"search": "Integrációk keresése"
|
||||
},
|
||||
"introduction": "Itt a komponenseket és a Home Assistant szervert lehet beállítani. Még nem lehet mindent a felületről, de dolgozunk rajta.",
|
||||
"introduction": "Ebben a nézetben lehetőség van a komponensek és a Home Assistant beállítására. Még nem lehet mindent a felületről, de dolgozunk rajta.",
|
||||
"logs": {
|
||||
"caption": "Napló",
|
||||
"clear": "Törlés",
|
||||
@@ -1567,7 +1602,7 @@
|
||||
"open": "Megnyitás"
|
||||
}
|
||||
},
|
||||
"description": "Lovelace irányítópultjainak konfigurálása",
|
||||
"description": "Lovelace irányítópultok kezelése",
|
||||
"resources": {
|
||||
"cant_edit_yaml": "A Lovelace-t YAML módban használod, ezért az erőforrásokat nem kezelheted a felhasználói felületről. Kezeld őket a configuration.yaml fájlban.",
|
||||
"caption": "Erőforrások",
|
||||
@@ -1645,7 +1680,7 @@
|
||||
"scene": {
|
||||
"activated": "Aktivált jelenet: {name}.",
|
||||
"caption": "Jelenetek",
|
||||
"description": "Jelenetek létrehozása és szerkesztése",
|
||||
"description": "Jelenetek kezelése",
|
||||
"editor": {
|
||||
"default_name": "Új jelenet",
|
||||
"devices": {
|
||||
@@ -1689,7 +1724,7 @@
|
||||
},
|
||||
"script": {
|
||||
"caption": "Szkriptek",
|
||||
"description": "Szkriptek létrehozása és szerkesztése",
|
||||
"description": "Szkriptek kezelése",
|
||||
"editor": {
|
||||
"alias": "Név",
|
||||
"default_name": "Új Szkript",
|
||||
@@ -1738,7 +1773,7 @@
|
||||
"reloading": {
|
||||
"automation": "Automatizálások újratöltése",
|
||||
"core": "Lokáció és testreszabások újratöltése",
|
||||
"group": "Csoportok újratöltése",
|
||||
"group": "Csoportok, csoport entitások és értesítési szolgáltatások újratöltése",
|
||||
"heading": "YAML konfiguráció újratöltése",
|
||||
"input_boolean": "Bemeneti logikai változók újratöltése",
|
||||
"input_datetime": "Időpont bemenetek újratöltése",
|
||||
@@ -1768,6 +1803,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"never_scanned": "Sosem volt szkennelve"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Felhasználó hozzáadása",
|
||||
@@ -1871,7 +1909,7 @@
|
||||
"create_group": "Zigbee Home Automation - Csoport létrehozása",
|
||||
"create_group_details": "Add meg a szükséges adatokat egy új zigbee csoport létrehozásához",
|
||||
"creating_group": "Csoport létrehozása",
|
||||
"description": "Zigbee csoportok létrehozása és módosítása",
|
||||
"description": "Zigbee csoportok kezelése",
|
||||
"group_details": "Itt van minden részlet a kiválasztott zigbee csoportról.",
|
||||
"group_id": "Csoport ID",
|
||||
"group_info": "Csoportinformációk",
|
||||
@@ -2143,6 +2181,9 @@
|
||||
"description": "A Gomb kártya gombok hozzáadását teszi lehetővé feladatok végrehajtásához.",
|
||||
"name": "Gomb"
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Naptár"
|
||||
},
|
||||
"conditional": {
|
||||
"card": "Kártya",
|
||||
"change_type": "Típus módosítása",
|
||||
@@ -2319,7 +2360,7 @@
|
||||
"show_code_editor": "Kódszerkesztő megjelenítése",
|
||||
"show_visual_editor": "Vizuális szerkesztő megjelenítése",
|
||||
"toggle_editor": "Szerkesztő",
|
||||
"typed_header": "{típusú} Kártya Konfiguráció",
|
||||
"typed_header": "{type} Kártya Konfiguráció",
|
||||
"unsaved_changes": "Vannak nem mentett módosítások"
|
||||
},
|
||||
"edit_lovelace": {
|
||||
@@ -2396,7 +2437,7 @@
|
||||
},
|
||||
"menu": {
|
||||
"close": "Bezárás",
|
||||
"configure_ui": "Felhasználói felület konfigurálása",
|
||||
"configure_ui": "Irányítópult szerkesztése",
|
||||
"exit_edit_mode": "Kilépés a felhasználói felület szerkesztési módból",
|
||||
"help": "Súgó",
|
||||
"refresh": "Frissítés",
|
||||
@@ -2589,6 +2630,7 @@
|
||||
"intro": "Hello {name}, üdvözöl a Home Assistant. Hogyan szeretnéd elnevezni az otthonodat?",
|
||||
"intro_location": "Szeretnénk tudni, hogy hol élsz. Ez segít az információk megjelenítésében és a nap alapú automatizálások beállításában. Ez az adat soha nem lesz megosztva a hálózatodon kívül.",
|
||||
"intro_location_detect": "Segíthetünk neked kitölteni ezt az információt egy külső szolgáltatás egyszeri lekérdezésével.",
|
||||
"location_name": "A Home Assistant rendszered neve",
|
||||
"location_name_default": "Otthon"
|
||||
},
|
||||
"integration": {
|
||||
@@ -2659,7 +2701,7 @@
|
||||
"learn_auth_requests": "Tudj meg többet a hitelesített kérelmek létrehozásáról.",
|
||||
"not_used": "Sosem használt",
|
||||
"prompt_copy_token": "Most másold ki a hozzáférési tokened! Erre később nem lesz lehetőséged.",
|
||||
"prompt_name": "Név?"
|
||||
"prompt_name": "Adj nevet a tokennek"
|
||||
},
|
||||
"mfa_setup": {
|
||||
"close": "Bezárás",
|
||||
|
@@ -567,6 +567,9 @@
|
||||
"loading_history": "Caricamento storico...",
|
||||
"no_history_found": "Nessuno storico trovato."
|
||||
},
|
||||
"logbook": {
|
||||
"entries_not_found": "Non sono state trovate voci nel registro."
|
||||
},
|
||||
"media-browser": {
|
||||
"audio_not_supported": "Il tuo browser non supporta l'elemento audio.",
|
||||
"choose_player": "Scegli il lettore",
|
||||
@@ -703,6 +706,7 @@
|
||||
},
|
||||
"more_info_control": {
|
||||
"controls": "Controlli",
|
||||
"details": "Dettagli",
|
||||
"dismiss": "Chiudi finestra di dialogo",
|
||||
"edit": "Modifica entità",
|
||||
"history": "Storico",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user