Merge pull request #6821 from home-assistant/dev

This commit is contained in:
Bram Kragten 2020-09-07 20:53:40 +02:00 committed by GitHub
commit 4999f1ad51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 1621 additions and 506 deletions

View File

@ -88,8 +88,8 @@ class HassioAddonNetwork extends LitElement {
</div>
<div class="card-actions">
<ha-progress-button class="warning" @click=${this._resetTapped}>
Reset to defaults </ha-progress-button
>>
Reset to defaults
</ha-progress-button>
<ha-progress-button @click=${this._saveTapped}>
Save
</ha-progress-button>

View File

@ -164,8 +164,9 @@ export class HassioUpdate extends LitElement {
try {
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
} catch (err) {
// Only show an error if the status code was not 504, or no status at all (connection terminated)
if (err.status_code && err.status_code !== 504) {
// 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)) {
showAlertDialog(this, {
title: "Update failed",
text: extractApiErrorMessage(err),

View File

@ -18,6 +18,7 @@ import {
setSupervisorOption,
SupervisorOptions,
updateSupervisor,
fetchHassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import {
showAlertDialog,
@ -176,10 +177,11 @@ class HassioSupervisorInfo extends LitElement {
try {
const data: Partial<SupervisorOptions> = {
channel: this.supervisorInfo.channel !== "stable" ? "beta" : "stable",
channel: this.supervisorInfo.channel === "stable" ? "beta" : "stable",
};
await setSupervisorOption(this.hass, data);
await reloadSupervisor(this.hass);
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to set supervisor option",
@ -195,6 +197,7 @@ class HassioSupervisorInfo extends LitElement {
try {
await reloadSupervisor(this.hass);
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to reload the supervisor",

View File

@ -79,6 +79,7 @@
"@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.5.0",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/sortablejs": "^1.10.6",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@vue/web-component-wrapper": "^1.2.0",

View File

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

View File

@ -0,0 +1,178 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "./state-badge";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
const rowRenderer = (root: HTMLElement, _owner, model: { item: string }) => {
if (!root.firstElementChild) {
root.innerHTML = `
<style>
paper-item {
margin: -10px;
padding: 0;
}
</style>
<paper-item></paper-item>
`;
}
root.querySelector("paper-item")!.textContent = model.item;
};
@customElement("ha-entity-attribute-picker")
class HaEntityAttributePicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId?: string;
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean, attribute: "allow-custom-value" })
public allowCustomValue;
@property() public label?: string;
@property() public value?: string;
@property({ type: Boolean }) private _opened = false;
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
protected shouldUpdate(changedProps: PropertyValues) {
return !(!changedProps.has("_opened") && this._opened);
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("_opened") && this._opened) {
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
(this._comboBox as any).items = state
? Object.keys(state.attributes)
: [];
}
}
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<vaadin-combo-box-light
.value=${this._value}
.allowCustomValue=${this.allowCustomValue}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._valueChanged}
>
<paper-input
.autofocus=${this.autofocus}
.label=${this.label ??
this.hass.localize(
"ui.components.entity.entity-attribute-picker.attribute"
)}
.value=${this._value}
.disabled=${this.disabled || !this.entityId}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
${this.value
? html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.entity.entity-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
Clear
</ha-icon-button>
`
: ""}
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.entity.entity-attribute-picker.show_attributes"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</ha-icon-button>
</paper-input>
</vaadin-combo-box-light>
`;
}
private _clearValue(ev: Event) {
ev.stopPropagation();
this._setValue("");
}
private get _value() {
return this.value || "";
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private _valueChanged(ev: PolymerChangedEvent<string>) {
const newValue = ev.detail.value;
if (newValue !== this._value) {
this._setValue(newValue);
}
}
private _setValue(value: string) {
this.value = value;
setTimeout(() => {
fireEvent(this, "value-changed", { value });
fireEvent(this, "change");
}, 0);
}
static get styles(): CSSResult {
return css`
paper-input > ha-icon-button {
--mdc-icon-button-size: 24px;
padding: 0px 2px;
color: var(--secondary-text-color);
}
[hidden] {
display: none;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-entity-attribute-picker": HaEntityAttributePicker;
}
}

View File

@ -1,4 +1,3 @@
import "../ha-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
@ -20,6 +19,7 @@ import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "./state-badge";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
@ -95,6 +95,8 @@ class HaEntityPicker extends LitElement {
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
private _initedStates = false;
private _getStates = memoizeOne(
(
_opened: boolean,
@ -148,11 +150,18 @@ class HaEntityPicker extends LitElement {
);
protected shouldUpdate(changedProps: PropertyValues) {
if (
changedProps.has("value") ||
changedProps.has("label") ||
changedProps.has("disabled")
) {
return true;
}
return !(!changedProps.has("_opened") && this._opened);
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("_opened") && this._opened) {
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
const states = this._getStates(
this._opened,
this.hass,
@ -162,6 +171,7 @@ class HaEntityPicker extends LitElement {
this.includeDeviceClasses
);
(this._comboBox as any).items = states;
this._initedStates = true;
}
}
@ -169,7 +179,6 @@ class HaEntityPicker extends LitElement {
if (!this.hass) {
return html``;
}
return html`
<vaadin-combo-box-light
item-value-path="entity_id"

View File

@ -64,6 +64,7 @@ export class HaDialog extends MwcDialog {
}
.mdc-dialog .mdc-dialog__surface {
position: var(--dialog-surface-position, relative);
top: var(--dialog-surface-top);
min-height: var(--mdc-dialog-min-height, auto);
}
:host([flexContent]) .mdc-dialog .mdc-dialog__content {

View File

@ -1,77 +0,0 @@
import { html } from "lit-element";
export const sortStyles = html`
<style>
#sortable a:nth-of-type(2n) paper-icon-item {
animation-name: keyframes1;
animation-iteration-count: infinite;
transform-origin: 50% 10%;
animation-delay: -0.75s;
animation-duration: 0.25s;
}
#sortable a:nth-of-type(2n-1) paper-icon-item {
animation-name: keyframes2;
animation-iteration-count: infinite;
animation-direction: alternate;
transform-origin: 30% 5%;
animation-delay: -0.5s;
animation-duration: 0.33s;
}
#sortable {
outline: none;
display: flex;
flex-direction: column;
}
.sortable-ghost {
opacity: 0.4;
}
.sortable-fallback {
opacity: 0;
}
@keyframes keyframes1 {
0% {
transform: rotate(-1deg);
animation-timing-function: ease-in;
}
50% {
transform: rotate(1.5deg);
animation-timing-function: ease-out;
}
}
@keyframes keyframes2 {
0% {
transform: rotate(1deg);
animation-timing-function: ease-in;
}
50% {
transform: rotate(-1.5deg);
animation-timing-function: ease-out;
}
}
.hide-panel {
display: none;
position: absolute;
right: 8px;
}
:host([expanded]) .hide-panel {
display: inline-flex;
}
paper-icon-item.hidden-panel,
paper-icon-item.hidden-panel span,
paper-icon-item.hidden-panel ha-icon[slot="item-icon"] {
color: var(--secondary-text-color);
cursor: pointer;
}
</style>
`;

View File

@ -23,7 +23,6 @@ import {
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { guard } from "lit-html/directives/guard";
@ -31,6 +30,7 @@ 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";
@ -160,7 +160,7 @@ const computePanels = memoizeOne(
let Sortable;
let sortStyles: TemplateResult;
let sortStyles: CSSResult;
@customElement("ha-sidebar")
class HaSidebar extends LitElement {
@ -228,7 +228,13 @@ class HaSidebar extends LitElement {
}
return html`
${this._editMode ? sortStyles : ""}
${this._editMode
? html`
<style>
${sortStyles?.cssText}
</style>
`
: ""}
<div class="menu">
${!this.narrow
? html`
@ -480,10 +486,10 @@ class HaSidebar extends LitElement {
if (!Sortable) {
const [sortableImport, sortStylesImport] = await Promise.all([
import("sortablejs/modular/sortable.core.esm"),
import("./ha-sidebar-sort-styles"),
import("../resources/ha-sortable-style"),
]);
sortStyles = sortStylesImport.sortStyles;
sortStyles = sortStylesImport.sortableStyles;
Sortable = sortableImport.Sortable;
Sortable.mount(sortableImport.OnSpill);
@ -649,6 +655,10 @@ class HaSidebar extends LitElement {
);
}
private _handlePanelTap(ev: Event) {
navigate(this, (ev.currentTarget as HTMLAnchorElement).href);
}
private _renderPanel(
urlPath: string,
title: string | null,
@ -661,6 +671,7 @@ class HaSidebar extends LitElement {
href="${`/${urlPath}`}"
data-panel="${urlPath}"
tabindex="-1"
@tap=${this._handlePanelTap}
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
>

View File

@ -43,7 +43,7 @@ class DialogMediaPlayerBrowse extends LitElement {
public closeDialog() {
this._params = undefined;
fireEvent(this, "dialog-closed", {dialog: this.localName});
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
@ -93,6 +93,9 @@ class DialogMediaPlayerBrowse extends LitElement {
@media (min-width: 800px) {
ha-dialog {
--mdc-dialog-max-width: 800px;
--dialog-surface-position: fixed;
--dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
}
ha-media-player-browse {
width: 700px;

View File

@ -18,8 +18,10 @@ import {
} from "lit-element";
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 { compare } from "../../common/string/compare";
import { computeRTLDirection } from "../../common/util/compute_rtl";
import { debounce } from "../../common/util/debounce";
import {
@ -30,6 +32,7 @@ import {
MediaPlayerBrowseAction,
} from "../../data/media-player";
import type { MediaPlayerItem } from "../../data/media-player";
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
@ -65,6 +68,8 @@ export class HaMediaPlayerBrowse extends LitElement {
@internalProperty() private _loading = false;
@internalProperty() private _error?: { message: string; code: string };
@internalProperty() private _mediaPlayerItems: MediaPlayerItem[] = [];
private _resizeObserver?: ResizeObserver;
@ -89,11 +94,55 @@ 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>`;
}
if (this._error && !this._mediaPlayerItems.length) {
if (this.dialog) {
this._closeDialogAction();
showAlertDialog(this, {
title: this.hass.localize(
"ui.components.media-browser.media_browsing_error"
),
text: this._renderError(this._error),
});
} else {
return html`<div class="container error">
${this._renderError(this._error)}
</div>`;
}
}
if (!this._mediaPlayerItems.length) {
return html``;
}
@ -130,7 +179,11 @@ export class HaMediaPlayerBrowse extends LitElement {
? html`
<div
class="img"
style="background-image: url(${currentItem.thumbnail})"
style=${styleMap({
backgroundImage: currentItem.thumbnail
? `url(${currentItem.thumbnail})`
: "none",
})}
>
${this._narrow && currentItem?.can_play
? html`
@ -209,7 +262,11 @@ export class HaMediaPlayerBrowse extends LitElement {
`
: ""}
</div>
${currentItem.children?.length
${this._error
? html`<div class="container error">
${this._renderError(this._error)}
</div>`
: currentItem.children?.length
? hasExpandableChildren
? html`
<div class="children">
@ -218,12 +275,16 @@ export class HaMediaPlayerBrowse extends LitElement {
<div
class="child"
.item=${child}
@click=${this._navigateForward}
@click=${this._childClicked}
>
<div class="ha-card-parent">
<ha-card
outlined
style="background-image: url(${child.thumbnail})"
style=${styleMap({
backgroundImage: child.thumbnail
? `url(${child.thumbnail})`
: "none",
})}
>
${child.can_expand && !child.thumbnail
? html`
@ -305,7 +366,9 @@ export class HaMediaPlayerBrowse extends LitElement {
)}
</mwc-list>
`
: this.hass.localize("ui.components.media-browser.no_items")}
: html`<div class="container">
${this.hass.localize("ui.components.media-browser.no_items")}
</div>`}
`;
}
@ -331,11 +394,22 @@ export class HaMediaPlayerBrowse extends LitElement {
return;
}
this._fetchData(this.mediaContentId, this.mediaContentType).then(
(itemData) => {
if (changedProps.has("entityId")) {
this._error = undefined;
this._mediaPlayerItems = [];
}
this._fetchData(this.mediaContentId, this.mediaContentType)
.then((itemData) => {
if (!itemData) {
return;
}
this._mediaPlayerItems = [itemData];
}
);
})
.catch((err) => {
this._error = err;
});
}
private _actionClicked(ev: MouseEvent): void {
@ -349,21 +423,41 @@ export class HaMediaPlayerBrowse extends LitElement {
fireEvent(this, "media-picked", { item });
}
private async _navigateForward(ev: MouseEvent): Promise<void> {
private async _childClicked(ev: MouseEvent): Promise<void> {
const target = ev.currentTarget as any;
const item: MediaPlayerItem = target.item;
if (!item) {
return;
}
if (!item.can_expand) {
this._runAction(item);
return;
}
this._navigate(item);
}
private async _navigate(item: MediaPlayerItem) {
const itemData = await this._fetchData(
item.media_content_id,
item.media_content_type
);
this._error = undefined;
let itemData: MediaPlayerItem;
try {
itemData = await this._fetchData(
item.media_content_id,
item.media_content_type
);
} catch (err) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.components.media-browser.media_browsing_error"
),
text: this._renderError(err),
});
return;
}
this.scrollTo(0, 0);
this._mediaPlayerItems = [...this._mediaPlayerItems, itemData];
@ -382,6 +476,13 @@ export class HaMediaPlayerBrowse extends LitElement {
mediaContentType
)
: await browseLocalMediaPlayer(this.hass, mediaContentId);
itemData.children = itemData.children?.sort((first, second) =>
!first.can_expand && second.can_expand
? 1
: first.can_expand && !second.can_expand
? -1
: compare(first.title, second.title)
);
return itemData;
}
@ -409,8 +510,8 @@ export class HaMediaPlayerBrowse extends LitElement {
this._resizeObserver.observe(this);
}
private _hasExpandableChildren = memoizeOne((children) =>
children.find((item: MediaPlayerItem) => item.can_expand)
private _hasExpandableChildren = memoizeOne((children?: MediaPlayerItem[]) =>
children?.find((item: MediaPlayerItem) => item.can_expand)
);
private _closeDialogAction(): void {
@ -429,18 +530,16 @@ export class HaMediaPlayerBrowse extends LitElement {
flex-direction: column;
}
.container {
padding: 16px;
}
.header {
display: flex;
justify-content: space-between;
border-bottom: 1px solid var(--divider-color);
}
.header_button {
position: relative;
top: 14px;
right: -8px;
}
.header {
background-color: var(--card-background-color);
position: sticky;
@ -539,9 +638,6 @@ export class HaMediaPlayerBrowse extends LitElement {
);
grid-gap: 16px;
margin: 8px 0px;
}
:host(:not([narrow])) .children {
padding: 0px 24px;
}
@ -683,8 +779,7 @@ export class HaMediaPlayerBrowse extends LitElement {
padding: 20px 24px 10px;
}
:host([narrow]) .media-source,
:host([narrow]) .children {
:host([narrow]) .media-source {
padding: 0 24px;
}
@ -703,8 +798,8 @@ export class HaMediaPlayerBrowse extends LitElement {
-webkit-line-clamp: 1;
}
:host(:not([narrow])[scroll]) .header-info {
height: 75px;
:host(:not([narrow])[scroll]) .header:not(.no-img) mwc-icon-button {
align-self: center;
}
:host([scroll]) .header-info mwc-button,

View File

@ -3,7 +3,7 @@ import {
HassEntityBase,
} from "home-assistant-js-websocket";
import { navigate } from "../common/navigate";
import { HomeAssistant, Context } from "../types";
import { Context, HomeAssistant } from "../types";
import { DeviceCondition, DeviceTrigger } from "./device_automation";
import { Action } from "./script";
@ -15,6 +15,7 @@ export interface AutomationEntity extends HassEntityBase {
}
export interface AutomationConfig {
id?: string;
alias: string;
description: string;
trigger: Trigger[];
@ -32,7 +33,8 @@ export interface ForDict {
export interface StateTrigger {
platform: "state";
entity_id?: string;
entity_id: string;
attribute?: string;
from?: string | number;
to?: string | number;
for?: string | number | ForDict;
@ -59,6 +61,7 @@ export interface HassTrigger {
export interface NumericStateTrigger {
platform: "numeric_state";
entity_id: string;
attribute?: string;
above?: number;
below?: number;
value_template?: string;
@ -136,12 +139,14 @@ export interface LogicalCondition {
export interface StateCondition {
condition: "state";
entity_id: string;
attribute?: string;
state: string | number;
}
export interface NumericStateCondition {
condition: "numeric_state";
entity_id: string;
attribute?: string;
above?: number;
below?: number;
value_template?: string;

View File

@ -13,6 +13,8 @@ export const DISCOVERY_SOURCES = [
"discovery",
];
export const ATTENTION_SOURCES = ["reauth"];
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
handler,

View File

@ -9,7 +9,7 @@ export const hassioApiResultExtractor = <T>(response: HassioResponse<T>) =>
export const extractApiErrorMessage = (error: any): string => {
return typeof error === "object"
? typeof error.body === "object"
? error.body.message || "Unkown error, see logs"
: error.body || "Unkown error, see logs"
? error.body.message || "Unknown error, see logs"
: error.body || "Unknown error, see logs"
: error;
};

View File

@ -5,7 +5,7 @@ import {
import { computeObjectId } from "../common/entity/compute_object_id";
import { navigate } from "../common/navigate";
import { HomeAssistant } from "../types";
import { Condition } from "./automation";
import { Condition, Trigger } from "./automation";
export const MODES = ["single", "restart", "queued", "parallel"];
export const MODES_MAX = ["queued", "parallel"];
@ -56,6 +56,13 @@ export interface SceneAction {
export interface WaitAction {
wait_template: string;
timeout?: number;
continue_on_timeout?: boolean;
}
export interface WaitForTriggerAction {
wait_for_trigger: Trigger[];
timeout?: number;
continue_on_timeout?: boolean;
}
export interface RepeatAction {
@ -91,6 +98,7 @@ export type Action =
| DelayAction
| SceneAction
| WaitAction
| WaitForTriggerAction
| RepeatAction
| ChooseAction;

View File

@ -5,19 +5,19 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-dialog";
import "../../components/ha-switch";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { DialogParams } from "./show-dialog-box";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("dialog-box")
class DialogBox extends LitElement {
@ -114,8 +114,8 @@ class DialogBox extends LitElement {
}
private _dismiss(): void {
if (this._params!.cancel) {
this._params!.cancel();
if (this._params?.cancel) {
this._params.cancel();
}
this._close();
}

View File

@ -130,7 +130,7 @@ class MoreInfoMediaPlayer extends LitElement {
</div>
`
: ""}
${stateObj.state !== "off" &&
${![UNAVAILABLE, UNKNOWN, "off"].includes(stateObj.state) &&
supportsFeature(stateObj, SUPPORT_SELECT_SOURCE) &&
stateObj.attributes.source_list?.length
? html`

View File

@ -48,7 +48,7 @@
}
@media (prefers-color-scheme: dark) {
html {
background-color: var(--primary-background-color, #111111);
background-color: #111111;
}
#ha-init-skeleton::before {
background-color: #1c1c1c;

View File

@ -5,6 +5,15 @@
<link rel="preload" href="<%= latestPageJS %>" as="script" crossorigin="use-credentials" />
<%= renderTemplate('_header') %>
<style>
html {
color: var(--primary-text-color, #212121);
}
@media (prefers-color-scheme: dark) {
html {
background-color: #111111;
color: var(--primary-text-color, #e1e1e1);
}
}
.content {
padding: 20px 16px;
max-width: 400px;
@ -23,14 +32,6 @@
.header img {
margin-right: 16px;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #111111;
color: #e1e1e1;
--primary-text-color: #e1e1e1;
--secondary-text-color: #9b9b9b;
}
}
</style>
</head>
<body>

View File

@ -63,6 +63,7 @@ class HassErrorScreen extends LitElement {
pointer-events: auto;
}
.content {
color: var(--primary-text-color);
height: calc(100% - 64px);
display: flex;
align-items: center;

View File

@ -1,9 +1,8 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-icon-button";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-svg-icon";
import { mdiDotsVertical, mdiArrowUp, mdiArrowDown } from "@mdi/js";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
@ -12,29 +11,31 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
} from "lit-element";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
import "../../../../components/ha-svg-icon";
import type { Action } from "../../../../data/script";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
import "./types/ha-automation-action-choose";
import "./types/ha-automation-action-condition";
import "./types/ha-automation-action-delay";
import "./types/ha-automation-action-device_id";
import "./types/ha-automation-action-event";
import "./types/ha-automation-action-repeat";
import "./types/ha-automation-action-scene";
import "./types/ha-automation-action-service";
import "./types/ha-automation-action-wait_for_trigger";
import "./types/ha-automation-action-wait_template";
import "./types/ha-automation-action-repeat";
import "./types/ha-automation-action-choose";
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import { haStyle } from "../../../../resources/styles";
const OPTIONS = [
"condition",
@ -44,6 +45,7 @@ const OPTIONS = [
"scene",
"service",
"wait_template",
"wait_for_trigger",
"repeat",
"choose",
];
@ -166,7 +168,7 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
@ -261,6 +263,7 @@ export default class HaAutomationActionRow extends LitElement {
this._switchYamlMode();
break;
case 1:
fireEvent(this, "duplicate");
break;
case 2:
this._onDelete();

View File

@ -28,6 +28,7 @@ export default class HaAutomationAction extends LitElement {
.index=${idx}
.totalActions=${this.actions.length}
.action=${action}
@duplicate=${this._duplicateAction}
@move-action=${this._move}
@value-changed=${this._actionChanged}
.hass=${this.hass}
@ -78,6 +79,14 @@ export default class HaAutomationAction extends LitElement {
fireEvent(this, "value-changed", { value: actions });
}
private _duplicateAction(ev: CustomEvent) {
ev.stopPropagation();
const index = (ev.target as any).index;
fireEvent(this, "value-changed", {
value: this.actions.concat(this.actions[index]),
});
}
static get styles(): CSSResult {
return css`
ha-automation-action-row,

View File

@ -1,22 +1,21 @@
import { mdiDelete } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
customElement,
LitElement,
property,
CSSResult,
css,
} from "lit-element";
import { html } from "lit-html";
import { Action, ChooseAction } from "../../../../../data/script";
import { HomeAssistant } from "../../../../../types";
import { ActionElement } from "../ha-automation-action-row";
import "../../condition/ha-automation-condition-editor";
import "@polymer/paper-listbox/paper-listbox";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../ha-automation-action";
import { Condition } from "../../../../../data/automation";
import { Action, ChooseAction } from "../../../../../data/script";
import { haStyle } from "../../../../../resources/styles";
import { mdiDelete } from "@mdi/js";
import { HomeAssistant } from "../../../../../types";
import "../ha-automation-action";
import { ActionElement } from "../ha-automation-action-row";
@customElement("ha-automation-action-choose")
export class HaChooseAction extends LitElement implements ActionElement {

View File

@ -1,22 +1,21 @@
import "@polymer/paper-input/paper-input";
import { customElement, LitElement, property, CSSResult } from "lit-element";
import { html } from "lit-html";
import {
RepeatAction,
Action,
CountRepeat,
WhileRepeat,
UntilRepeat,
} from "../../../../../data/script";
import { HomeAssistant } from "../../../../../types";
import { ActionElement } from "../ha-automation-action-row";
import "../../condition/ha-automation-condition-editor";
import type { PaperListboxElement } from "@polymer/paper-listbox";
import "@polymer/paper-listbox/paper-listbox";
import { CSSResult, customElement, LitElement, property } from "lit-element";
import { html } from "lit-html";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../ha-automation-action";
import { Condition } from "../../../../lovelace/common/validate-condition";
import {
Action,
CountRepeat,
RepeatAction,
UntilRepeat,
WhileRepeat,
} from "../../../../../data/script";
import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { Condition } from "../../../../lovelace/common/validate-condition";
import "../ha-automation-action";
import { ActionElement } from "../ha-automation-action-row";
const OPTIONS = ["count", "while", "until"];

View File

@ -8,6 +8,7 @@ import {
} from "lit-element";
import { html } from "lit-html";
import memoizeOne from "memoize-one";
import { any, assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { computeDomain } from "../../../../../common/entity/compute_domain";
import { computeObjectId } from "../../../../../common/entity/compute_object_id";
@ -18,14 +19,13 @@ import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
import { ServiceAction } from "../../../../../data/script";
import type { PolymerChangedEvent } from "../../../../../polymer-types";
import type { HomeAssistant } from "../../../../../types";
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
import { assert, optional, object, string } from "superstruct";
import { EntityId } from "../../../../lovelace/common/structs/is-entity-id";
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
const actionStruct = object({
service: optional(string()),
entity_id: optional(EntityId),
data: optional(object()),
data: optional(any()),
});
@customElement("ha-automation-action-service")

View File

@ -0,0 +1,70 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { customElement, LitElement, property } from "lit-element";
import { html } from "lit-html";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-formfield";
import { WaitForTriggerAction } from "../../../../../data/script";
import { HomeAssistant } from "../../../../../types";
import "../../trigger/ha-automation-trigger";
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
@customElement("ha-automation-action-wait_for_trigger")
export class HaWaitForTriggerAction extends LitElement
implements ActionElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public action!: WaitForTriggerAction;
public static get defaultConfig() {
return { wait_for_trigger: [], timeout: "" };
}
protected render() {
const { wait_for_trigger, continue_on_timeout, timeout } = this.action;
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
)}
.name=${"timeout"}
.value=${timeout}
@value-changed=${this._valueChanged}
></paper-input>
<br />
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
)}
>
<ha-switch
.checked=${continue_on_timeout}
@change=${this._continueChanged}
></ha-switch>
</ha-formfield>
<ha-automation-trigger
.triggers=${wait_for_trigger}
.hass=${this.hass}
.name=${"wait_for_trigger"}
@value-changed=${this._valueChanged}
></ha-automation-trigger>
`;
}
private _continueChanged(ev) {
fireEvent(this, "value-changed", {
value: { ...this.action, continue_on_timeout: ev.target.checked },
});
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-action-wait_for_trigger": HaWaitForTriggerAction;
}
}

View File

@ -2,6 +2,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { customElement, LitElement, property } from "lit-element";
import { html } from "lit-html";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { WaitAction } from "../../../../../data/script";
import { HomeAssistant } from "../../../../../types";
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
@ -13,11 +14,11 @@ export class HaWaitAction extends LitElement implements ActionElement {
@property() public action!: WaitAction;
public static get defaultConfig() {
return { wait_template: "", timeout: "" };
return { wait_template: "" };
}
protected render() {
const { wait_template, timeout } = this.action;
const { wait_template, timeout, continue_on_timeout } = this.action;
return html`
<paper-textarea
@ -37,9 +38,24 @@ export class HaWaitAction extends LitElement implements ActionElement {
.value=${timeout}
@value-changed=${this._valueChanged}
></paper-input>
<br />
<ha-formfield
.label=${this.hass.localize("ui.panel.config.automation.editor.actions.type.wait_template.continue_timeout")}
>
<ha-switch
.checked=${continue_on_timeout}
@change=${this._continueChanged}
></ha-switch>
</ha-formfield>
`;
}
private _continueChanged(ev) {
fireEvent(this, "value-changed", {
value: { ...this.action, continue_on_timeout: ev.target.checked },
});
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}

View File

@ -1,24 +1,24 @@
import "../../../../components/ha-icon-button";
import "@polymer/paper-item/paper-item";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import "../../../../components/ha-button-menu";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-item/paper-item";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
} from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
import "../../../../components/ha-icon-button";
import { Condition } from "../../../../data/automation";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../../types";
import "./ha-automation-condition-editor";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
export interface ConditionElement extends LitElement {
condition: Condition;
@ -81,7 +81,7 @@ export default class HaAutomationConditionRow extends LitElement {
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
@ -109,6 +109,7 @@ export default class HaAutomationConditionRow extends LitElement {
this._switchYamlMode();
break;
case 1:
fireEvent(this, "duplicate");
break;
case 2:
this._onDelete();

View File

@ -6,6 +6,7 @@ import {
html,
LitElement,
property,
PropertyValues,
} from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-card";
@ -20,13 +21,43 @@ export default class HaAutomationCondition extends LitElement {
@property() public conditions!: Condition[];
protected updated(changedProperties: PropertyValues) {
if (!changedProperties.has("conditions")) {
return;
}
let updatedConditions: Condition[] | undefined;
if (!Array.isArray(this.conditions)) {
updatedConditions = [this.conditions];
}
(updatedConditions || this.conditions).forEach((condition, index) => {
if (typeof condition === "string") {
updatedConditions = updatedConditions || [...this.conditions];
updatedConditions[index] = {
condition: "template",
value_template: condition,
};
}
});
if (updatedConditions) {
fireEvent(this, "value-changed", {
value: updatedConditions,
});
}
}
protected render() {
if (!Array.isArray(this.conditions)) {
return html``;
}
return html`
${this.conditions.map(
(cond, idx) => html`
<ha-automation-condition-row
.index=${idx}
.condition=${cond}
@duplicate=${this._duplicateCondition}
@value-changed=${this._conditionChanged}
.hass=${this.hass}
></ha-automation-condition-row>
@ -68,6 +99,14 @@ export default class HaAutomationCondition extends LitElement {
fireEvent(this, "value-changed", { value: conditions });
}
private _duplicateCondition(ev: CustomEvent) {
ev.stopPropagation();
const index = (ev.target as any).index;
fireEvent(this, "value-changed", {
value: this.conditions.concat(this.conditions[index]),
});
}
static get styles(): CSSResult {
return css`
ha-automation-condition-row,

View File

@ -1,7 +1,6 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-picker";
import { NumericStateCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
@ -19,16 +18,34 @@ export default class HaNumericStateCondition extends LitElement {
};
}
protected render() {
const { value_template, entity_id, below, above } = this.condition;
public render() {
const {
value_template,
entity_id,
attribute,
below,
above,
} = this.condition;
return html`
<ha-entity-picker
.value="${entity_id}"
@value-changed="${this._entityPicked}"
.value=${entity_id}
.name=${"entity_id"}
@value-changed=${this._valueChanged}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${entity_id}
.value=${attribute}
.name=${"attribute"}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.attribute"
)}
@value-changed=${this._valueChanged}
allow-custom-value
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.numeric_state.above"
@ -60,13 +77,6 @@ export default class HaNumericStateCondition extends LitElement {
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.condition, entity_id: ev.detail.value },
});
}
}
declare global {

View File

@ -1,9 +1,8 @@
import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import { StateCondition } from "../../../../../data/automation";
import { PolymerChangedEvent } from "../../../../../polymer-types";
import { HomeAssistant } from "../../../../../types";
import {
ConditionElement,
@ -21,15 +20,27 @@ export class HaStateCondition extends LitElement implements ConditionElement {
}
protected render() {
const { entity_id, state } = this.condition;
const { entity_id, attribute, state } = this.condition;
return html`
<ha-entity-picker
.value=${entity_id}
@value-changed=${this._entityPicked}
.name=${"entity_id"}
@value-changed=${this._valueChanged}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${entity_id}
.value=${attribute}
.name=${"attribute"}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.attribute"
)}
@value-changed=${this._valueChanged}
allow-custom-value
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.state.state"
@ -44,13 +55,6 @@ export class HaStateCondition extends LitElement implements ConditionElement {
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev: PolymerChangedEvent<string>) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.condition, entity_id: ev.detail.value },
});
}
}
declare global {

View File

@ -1,5 +1,14 @@
import { Radio } from "@material/mwc-radio";
import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import {
customElement,
html,
internalProperty,
LitElement,
property,
} from "lit-element";
import "../../../../../components/ha-formfield";
import "../../../../../components/ha-radio";
import { TimeCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import {
@ -7,38 +16,130 @@ import {
handleChangeEvent,
} from "../ha-automation-condition-row";
const includeDomains = ["input_datetime"];
@customElement("ha-automation-condition-time")
export class HaTimeCondition extends LitElement implements ConditionElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public condition!: TimeCondition;
@internalProperty() private _inputModeBefore?: boolean;
@internalProperty() private _inputModeAfter?: boolean;
public static get defaultConfig() {
return {};
}
protected render() {
const { after, before } = this.condition;
const inputModeBefore =
this._inputModeBefore ?? before?.startsWith("input_datetime.");
const inputModeAfter =
this._inputModeAfter ?? after?.startsWith("input_datetime.");
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.after"
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.conditions.type.time.type_value"
)}
name="after"
.value=${after}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.before"
>
<ha-radio
@change=${this._handleModeChanged}
name="mode_after"
value="value"
?checked=${!inputModeAfter}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.conditions.type.time.type_input"
)}
name="before"
.value=${before}
@value-changed=${this._valueChanged}
></paper-input>
>
<ha-radio
@change=${this._handleModeChanged}
name="mode_after"
value="input"
?checked=${inputModeAfter}
></ha-radio>
</ha-formfield>
${inputModeAfter
? html`<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.after"
)}
.includeDomains=${includeDomains}
.name=${"after"}
.value=${after?.startsWith("input_datetime.") ? after : ""}
@value-changed=${this._valueChanged}
.hass=${this.hass}
></ha-entity-picker>`
: html`<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.after"
)}
name="after"
.value=${after?.startsWith("input_datetime.") ? "" : after}
@value-changed=${this._valueChanged}
></paper-input>`}
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.conditions.type.time.type_value"
)}
>
<ha-radio
@change=${this._handleModeChanged}
name="mode_before"
value="value"
?checked=${!inputModeBefore}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.conditions.type.time.type_input"
)}
>
<ha-radio
@change=${this._handleModeChanged}
name="mode_before"
value="input"
?checked=${inputModeBefore}
></ha-radio>
</ha-formfield>
${inputModeBefore
? html`<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.before"
)}
.includeDomains=${includeDomains}
.name=${"before"}
.value=${before?.startsWith("input_datetime.") ? before : ""}
@value-changed=${this._valueChanged}
.hass=${this.hass}
></ha-entity-picker>`
: html`<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.before"
)}
name="before"
.value=${before?.startsWith("input_datetime.") ? "" : before}
@value-changed=${this._valueChanged}
></paper-input>`}
`;
}
private _handleModeChanged(ev: Event) {
const target = ev.target as Radio;
if (target.getAttribute("name") === "mode_after") {
this._inputModeAfter = target.value === "input";
} else {
this._inputModeBefore = target.value === "input";
}
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}

View File

@ -1,28 +1,32 @@
import "@material/mwc-fab";
import { mdiContentDuplicate, mdiContentSave, mdiDelete } from "@mdi/js";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-input/paper-textarea";
import "../../../components/ha-icon-button";
import { PaperListboxElement } from "@polymer/paper-listbox";
import {
css,
CSSResult,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { navigate } from "../../../common/navigate";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "@material/mwc-fab";
import {
AutomationConfig,
AutomationEntity,
Condition,
deleteAutomation,
getAutomationEditorInitData,
showAutomationEditor,
Trigger,
triggerAutomation,
} from "../../../data/automation";
@ -42,9 +46,6 @@ import { HaDeviceAction } from "./action/types/ha-automation-action-device_id";
import "./condition/ha-automation-condition";
import "./trigger/ha-automation-trigger";
import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device";
import { mdiContentSave } from "@mdi/js";
import { PaperListboxElement } from "@polymer/paper-listbox";
import { classMap } from "lit-html/directives/class-map";
const MODES = ["single", "restart", "queued", "parallel"];
const MODES_MAX = ["queued", "parallel"];
@ -53,6 +54,7 @@ declare global {
// for fire event
interface HASSDomEvents {
"ui-mode-not-available": Error;
duplicate: undefined;
}
}
@ -92,14 +94,24 @@ export class HaAutomationEditor extends LitElement {
${!this.automationId
? ""
: html`
<ha-icon-button
<mwc-icon-button
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.automation.picker.duplicate_automation"
)}"
@click=${this._duplicate}
>
<ha-svg-icon .path=${mdiContentDuplicate}></ha-svg-icon>
</mwc-icon-button>
<mwc-icon-button
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.automation.picker.delete_automation"
)}"
icon="hass:delete"
@click=${this._deleteConfirm}
></ha-icon-button>
>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
`}
${this._config
? html`
@ -473,6 +485,31 @@ export class HaAutomationEditor extends LitElement {
}
}
private async _duplicate() {
if (this._dirty) {
if (
!(await showConfirmationDialog(this, {
text: this.hass!.localize(
"ui.panel.config.automation.editor.unsaved_confirm"
),
confirmText: this.hass!.localize("ui.common.yes"),
dismissText: this.hass!.localize("ui.common.no"),
}))
) {
return;
}
// Wait for dialog to complate closing
await new Promise((resolve) => setTimeout(resolve, 0));
}
showAutomationEditor(this, {
...this._config,
id: undefined,
alias: `${this._config?.alias} (${this.hass.localize(
"ui.panel.config.automation.picker.duplicate"
)})`,
});
}
private async _deleteConfirm() {
showConfirmationDialog(this, {
text: this.hass.localize(

View File

@ -1,25 +1,27 @@
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "../../../../components/ha-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "@material/mwc-list/mwc-list-item";
import "../../../../components/ha-button-menu";
import { mdiDotsVertical } from "@mdi/js";
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
} from "lit-element";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
import "../../../../components/ha-icon-button";
import type { Trigger } from "../../../../data/automation";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "./types/ha-automation-trigger-device";
import "./types/ha-automation-trigger-event";
@ -29,14 +31,12 @@ import "./types/ha-automation-trigger-mqtt";
import "./types/ha-automation-trigger-numeric_state";
import "./types/ha-automation-trigger-state";
import "./types/ha-automation-trigger-sun";
import "./types/ha-automation-trigger-tag";
import "./types/ha-automation-trigger-template";
import "./types/ha-automation-trigger-time";
import "./types/ha-automation-trigger-time_pattern";
import "./types/ha-automation-trigger-webhook";
import "./types/ha-automation-trigger-zone";
import "./types/ha-automation-trigger-tag";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import { haStyle } from "../../../../resources/styles";
const OPTIONS = [
"device",
@ -113,7 +113,7 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
@ -183,6 +183,7 @@ export default class HaAutomationTriggerRow extends LitElement {
this._switchYamlMode();
break;
case 1:
fireEvent(this, "duplicate");
break;
case 2:
this._onDelete();

View File

@ -27,6 +27,7 @@ export default class HaAutomationTrigger extends LitElement {
<ha-automation-trigger-row
.index=${idx}
.trigger=${trg}
@duplicate=${this._duplicateTrigger}
@value-changed=${this._triggerChanged}
.hass=${this.hass}
></ha-automation-trigger-row>
@ -68,6 +69,14 @@ export default class HaAutomationTrigger extends LitElement {
fireEvent(this, "value-changed", { value: triggers });
}
private _duplicateTrigger(ev: CustomEvent) {
ev.stopPropagation();
const index = (ev.target as any).index;
fireEvent(this, "value-changed", {
value: this.triggers.concat(this.triggers[index]),
});
}
static get styles(): CSSResult {
return css`
ha-automation-trigger-row,

View File

@ -1,7 +1,6 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-picker";
import { ForDict, NumericStateTrigger } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
@ -19,8 +18,8 @@ export default class HaNumericStateTrigger extends LitElement {
};
}
protected render() {
const { value_template, entity_id, below, above } = this.trigger;
public render() {
const { value_template, entity_id, attribute, below, above } = this.trigger;
let trgFor = this.trigger.for;
if (
@ -41,10 +40,22 @@ export default class HaNumericStateTrigger extends LitElement {
return html`
<ha-entity-picker
.value="${entity_id}"
@value-changed="${this._entityPicked}"
@value-changed="${this._valueChanged}"
.name=${"entity_id"}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${entity_id}
.value=${attribute}
.name=${"attribute"}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.attribute"
)}
@value-changed=${this._valueChanged}
allow-custom-value
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.numeric_state.above"
@ -84,13 +95,6 @@ export default class HaNumericStateTrigger extends LitElement {
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, entity_id: ev.detail.value },
});
}
}
declare global {

View File

@ -1,9 +1,8 @@
import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import { ForDict, StateTrigger } from "../../../../../data/automation";
import { PolymerChangedEvent } from "../../../../../polymer-types";
import { HomeAssistant } from "../../../../../types";
import {
handleChangeEvent,
@ -21,7 +20,7 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
}
protected render() {
const { entity_id, to, from } = this.trigger;
const { entity_id, attribute, to, from } = this.trigger;
let trgFor = this.trigger.for;
if (
@ -43,10 +42,22 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
return html`
<ha-entity-picker
.value=${entity_id}
@value-changed=${this._entityPicked}
@value-changed=${this._valueChanged}
.name=${"entity_id"}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${entity_id}
.value=${attribute}
.name=${"attribute"}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.attribute"
)}
@value-changed=${this._valueChanged}
allow-custom-value
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.from"
@ -77,13 +88,6 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev: PolymerChangedEvent<string>) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, entity_id: ev.detail.value },
});
}
}
declare global {

View File

@ -1,5 +1,14 @@
import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import {
customElement,
html,
internalProperty,
LitElement,
property,
} from "lit-element";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-formfield";
import "../../../../../components/ha-radio";
import { TimeTrigger } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import {
@ -7,31 +16,81 @@ import {
TriggerElement,
} from "../ha-automation-trigger-row";
const includeDomains = ["input_datetime"];
@customElement("ha-automation-trigger-time")
export class HaTimeTrigger extends LitElement implements TriggerElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public trigger!: TimeTrigger;
@internalProperty() private _inputMode?: boolean;
public static get defaultConfig() {
return { at: "" };
}
protected render() {
const { at } = this.trigger;
const inputMode = this._inputMode ?? at?.startsWith("input_datetime.");
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time.at"
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.triggers.type.time.type_value"
)}
name="at"
.value=${at}
@value-changed=${this._valueChanged}
></paper-input>
>
<ha-radio
@change=${this._handleModeChanged}
name="mode"
value="value"
?checked=${!inputMode}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.triggers.type.time.type_input"
)}
>
<ha-radio
@change=${this._handleModeChanged}
name="mode"
value="input"
?checked=${inputMode}
></ha-radio>
</ha-formfield>
${inputMode
? html`<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time.at"
)}
.includeDomains=${includeDomains}
.name=${"at"}
.value=${at?.startsWith("input_datetime.") ? at : ""}
@value-changed=${this._valueChanged}
.hass=${this.hass}
></ha-entity-picker>`
: html`<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time.at"
)}
name="at"
.value=${at?.startsWith("input_datetime.") ? "" : at}
@value-changed=${this._valueChanged}
></paper-input>`}
`;
}
private _handleModeChanged(ev: Event) {
this._inputMode = (ev.target as any).value === "input";
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-time": HaTimeTrigger;
}
}

View File

@ -10,12 +10,13 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import "../../../common/search/search-input";
@ -32,6 +33,7 @@ import {
getConfigEntries,
} from "../../../data/config_entries";
import {
ATTENTION_SOURCES,
DISCOVERY_SOURCES,
getConfigFlowInProgressCollection,
ignoreConfigFlow,
@ -355,52 +357,67 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
: ""}
${configEntriesInProgress.length
? configEntriesInProgress.map(
(flow: DataEntryFlowProgressExtended) => html`
<ha-card outlined class="discovered">
<div class="header">
${this.hass.localize(
"ui.panel.config.integrations.discovered"
)}
</div>
<div class="card-content">
<div class="image">
<img
src="https://brands.home-assistant.io/${flow.handler}/logo.png"
referrerpolicy="no-referrer"
@error=${this._onImageError}
@load=${this._onImageLoad}
/>
(flow: DataEntryFlowProgressExtended) => {
const attention = ATTENTION_SOURCES.includes(
flow.context.source
);
return html`
<ha-card
outlined
class=${classMap({
discovered: !attention,
attention: attention,
})}
>
<div class="header">
${this.hass.localize(
`ui.panel.config.integrations.${
attention ? "attention" : "discovered"
}`
)}
</div>
<h2>
${flow.localized_title}
</h2>
<div>
<mwc-button
unelevated
@click=${this._continueFlow}
.flowId=${flow.flow_id}
>
${this.hass.localize(
"ui.panel.config.integrations.configure"
)}
</mwc-button>
${DISCOVERY_SOURCES.includes(flow.context.source) &&
flow.context.unique_id
? html`
<mwc-button
@click=${this._ignoreFlow}
.flow=${flow}
>
${this.hass.localize(
"ui.panel.config.integrations.ignore.ignore"
)}
</mwc-button>
`
: ""}
<div class="card-content">
<div class="image">
<img
src="https://brands.home-assistant.io/${flow.handler}/logo.png"
referrerpolicy="no-referrer"
@error=${this._onImageError}
@load=${this._onImageLoad}
/>
</div>
<h2>
${flow.localized_title}
</h2>
<div>
<mwc-button
unelevated
@click=${this._continueFlow}
.flowId=${flow.flow_id}
>
${this.hass.localize(
`ui.panel.config.integrations.${
attention ? "reconfigure" : "configure"
}`
)}
</mwc-button>
${DISCOVERY_SOURCES.includes(flow.context.source) &&
flow.context.unique_id
? html`
<mwc-button
@click=${this._ignoreFlow}
.flow=${flow}
>
${this.hass.localize(
"ui.panel.config.integrations.ignore.ignore"
)}
</mwc-button>
`
: ""}
</div>
</div>
</div>
</ha-card>
`
</ha-card>
`;
}
)
: ""}
${groupedConfigEntries.size
@ -639,6 +656,18 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
flex-direction: column;
justify-content: space-between;
}
.attention {
--ha-card-border-color: var(--error-color);
}
.attention .header {
background: var(--error-color);
color: var(--text-primary-color);
padding: 8px;
text-align: center;
}
.attention mwc-button {
--mdc-theme-primary: var(--error-color);
}
.discovered {
--ha-card-border-color: var(--primary-color);
}

View File

@ -137,6 +137,7 @@ export class HaIntegrationCard extends LitElement {
private _renderSingleEntry(item: ConfigEntryExtended): TemplateResult {
const devices = this._getDevices(item);
const services = this._getServices(item);
const entities = this._getEntities(item);
return html`
@ -168,7 +169,7 @@ export class HaIntegrationCard extends LitElement {
<h3>
${item.localized_domain_name === item.title ? "" : item.title}
</h3>
${devices.length || entities.length
${devices.length || services.length || entities.length
? html`
<div>
${devices.length
@ -180,10 +181,22 @@ export class HaIntegrationCard extends LitElement {
"count",
devices.length
)}</a
>${services.length ? "," : ""}
`
: ""}
${services.length
? html`
<a
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
>${this.hass.localize(
"ui.panel.config.integrations.config_entry.services",
"count",
services.length
)}</a
>
`
: ""}
${devices.length && entities.length
${(devices.length || services.length) && entities.length
? this.hass.localize("ui.common.and")
: ""}
${entities.length
@ -304,8 +317,21 @@ export class HaIntegrationCard extends LitElement {
if (!this.deviceRegistryEntries) {
return [];
}
return this.deviceRegistryEntries.filter((device) =>
device.config_entries.includes(configEntry.entry_id)
return this.deviceRegistryEntries.filter(
(device) =>
device.config_entries.includes(configEntry.entry_id) &&
device.entry_type !== "service"
);
}
private _getServices(configEntry: ConfigEntry): DeviceRegistryEntry[] {
if (!this.deviceRegistryEntries) {
return [];
}
return this.deviceRegistryEntries.filter(
(device) =>
device.config_entries.includes(configEntry.entry_id) &&
device.entry_type === "service"
);
}

View File

@ -216,7 +216,7 @@ class HaLogbook extends LitElement {
display: flex;
justify-content: center;
flex-direction: column;
width: 65px;
width: 70px;
flex-shrink: 0;
font-size: 12px;
color: var(--secondary-text-color);

View File

@ -44,6 +44,8 @@ class ActionHandler extends HTMLElement implements ActionHandler {
protected held = false;
private cancelled = false;
private dblClickTimeout?: number;
constructor() {
@ -76,9 +78,12 @@ class ActionHandler extends HTMLElement implements ActionHandler {
document.addEventListener(
ev,
() => {
clearTimeout(this.timer);
this.stopAnimation();
this.timer = undefined;
this.cancelled = true;
if (this.timer) {
this.stopAnimation();
clearTimeout(this.timer);
this.timer = undefined;
}
},
{ passive: true }
);
@ -124,7 +129,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
}
element.actionHandler.start = (ev: Event) => {
this.held = false;
this.cancelled = false;
let x;
let y;
if ((ev as TouchEvent).touches) {
@ -136,6 +141,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
}
if (options.hasHold) {
this.held = false;
this.timer = window.setTimeout(() => {
this.startAnimation(x, y);
this.held = true;
@ -144,24 +150,20 @@ class ActionHandler extends HTMLElement implements ActionHandler {
};
element.actionHandler.end = (ev: Event) => {
// Don't respond on our own generated click
if (!ev.isTrusted) {
// Don't respond when moved or scrolled while touch
if (["touchend", "touchcancel"].includes(ev.type) && this.cancelled) {
return;
}
// Prevent mouse event if touch event
ev.preventDefault();
if (ev.cancelable) {
ev.preventDefault();
}
if (options.hasHold) {
if (
["touchend", "touchcancel"].includes(ev.type) &&
this.timer === undefined
) {
return;
}
clearTimeout(this.timer);
this.stopAnimation();
this.timer = undefined;
}
if (this.held) {
if (options.hasHold && this.held) {
fireEvent(element, "action", { action: "hold" });
} else if (options.hasDoubleClick) {
if (
@ -179,8 +181,6 @@ class ActionHandler extends HTMLElement implements ActionHandler {
}
} else {
fireEvent(element, "action", { action: "tap" });
// Fire the click we prevented the action for
(ev.target as HTMLElement)?.click();
}
};

View File

@ -1,27 +1,51 @@
import "../../../components/ha-icon-button";
import { mdiDrag } from "@mdi/js";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { guard } from "lit-html/directives/guard";
import type { SortableEvent } from "sortablejs";
import Sortable, {
AutoScroll,
OnSpill,
} from "sortablejs/modular/sortable.core.esm";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../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")
export class HuiEntityEditor extends LitElement {
@property() protected hass?: HomeAssistant;
@property({ attribute: false }) protected hass?: HomeAssistant;
@property() protected entities?: EntityConfig[];
@property({ attribute: false }) protected entities?: EntityConfig[];
@property() protected label?: string;
@internalProperty() private _attached = false;
private _sortable?;
public connectedCallback() {
super.connectedCallback();
this._attached = true;
}
public disconnectedCallback() {
super.disconnectedCallback();
this._attached = false;
}
protected render(): TemplateResult {
if (!this.entities) {
return html``;
@ -36,42 +60,73 @@ export class HuiEntityEditor extends LitElement {
")"}
</h3>
<div class="entities">
${this.entities.map((entityConf, index) => {
return html`
<div class="entity">
<ha-entity-picker
.hass=${this.hass}
.value="${entityConf.entity}"
.index="${index}"
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<ha-icon-button
title="Move entity down"
icon="hass:arrow-down"
.index="${index}"
@click="${this._entityDown}"
?disabled="${index === this.entities!.length - 1}"
></ha-icon-button>
<ha-icon-button
title="Move entity up"
icon="hass:arrow-up"
.index="${index}"
@click="${this._entityUp}"
?disabled="${index === 0}"
></ha-icon-button>
</div>
`;
})}
<ha-entity-picker
.hass=${this.hass}
@change="${this._addEntity}"
></ha-entity-picker>
${guard([this.entities], () =>
this.entities!.map((entityConf, index) => {
return html`
<div class="entity" data-entity-id=${entityConf.entity}>
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
<ha-entity-picker
.hass=${this.hass}
.value=${entityConf.entity}
.index=${index}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
</div>
`;
})
)}
</div>
<ha-entity-picker
.hass=${this.hass}
@change=${this._addEntity}
></ha-entity-picker>
`;
}
private _addEntity(ev: Event): void {
protected firstUpdated(): void {
Sortable.mount(OnSpill);
Sortable.mount(new AutoScroll());
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
const attachedChanged = changedProps.has("_attached");
const entitiesChanged = changedProps.has("entities");
if (!entitiesChanged && !attachedChanged) {
return;
}
if (attachedChanged && !this._attached) {
// Tear down sortable, if available
this._sortable?.destroy();
this._sortable = undefined;
return;
}
if (!this._sortable && this.entities) {
this._createSortable();
return;
}
if (entitiesChanged) {
this._sortable.sort(this.entities?.map((entity) => entity.entity));
}
}
private _createSortable() {
this._sortable = new Sortable(this.shadowRoot!.querySelector(".entities"), {
animation: 150,
fallbackClass: "sortable-fallback",
handle: "ha-svg-icon",
dataIdAttr: "data-entity-id",
onEnd: async (evt: SortableEvent) => this._entityMoved(evt),
});
}
private async _addEntity(ev: Event): Promise<void> {
const target = ev.target! as EditorTarget;
if (target.value === "") {
return;
@ -83,26 +138,14 @@ export class HuiEntityEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newConfigEntities });
}
private _entityUp(ev: Event): void {
const target = ev.target! as EditorTarget;
private _entityMoved(ev: SortableEvent): void {
if (ev.oldIndex === ev.newIndex) {
return;
}
const newEntities = this.entities!.concat();
[newEntities[target.index! - 1], newEntities[target.index!]] = [
newEntities[target.index!],
newEntities[target.index! - 1],
];
fireEvent(this, "entities-changed", { entities: newEntities });
}
private _entityDown(ev: Event): void {
const target = ev.target! as EditorTarget;
const newEntities = this.entities!.concat();
[newEntities[target.index! + 1], newEntities[target.index!]] = [
newEntities[target.index!],
newEntities[target.index! + 1],
];
newEntities.splice(ev.newIndex!, 0, newEntities.splice(ev.oldIndex!, 1)[0]);
fireEvent(this, "entities-changed", { entities: newEntities });
}
@ -123,16 +166,23 @@ export class HuiEntityEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newConfigEntities });
}
static get styles(): CSSResult {
return css`
.entity {
display: flex;
align-items: flex-end;
}
.entity ha-entity-picker {
flex-grow: 1;
}
`;
static get styles(): CSSResult[] {
return [
sortableStyles,
css`
.entity {
display: flex;
align-items: center;
}
.entity ha-svg-icon {
padding-right: 8px;
cursor: move;
}
.entity ha-entity-picker {
flex-grow: 1;
}
`,
];
}
}

View File

@ -171,6 +171,7 @@ export class HuiCreateDialogCard extends LitElement implements HassDialog {
ha-dialog {
--mdc-dialog-max-width: 845px;
--dialog-content-padding: 2px 24px 20px 24px;
--dialog-z-index: 5;
}
ha-dialog.table {

View File

@ -366,6 +366,7 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
ha-dialog {
--mdc-dialog-max-width: 845px;
--dialog-z-index: 5;
}
ha-header-bar {

View File

@ -140,6 +140,7 @@ export class HuiDialogSuggestCard extends LitElement {
}
ha-paper-dialog {
max-width: 845px;
--dialog-z-index: 5;
}
mwc-button ha-circular-progress {
width: 14px;

View File

@ -2,13 +2,15 @@ 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/entity/ha-entity-attribute-picker";
import "../../../../components/ha-icon-input";
import { HomeAssistant } from "../../../../types";
import { EntityCardConfig } from "../../cards/types";
@ -19,7 +21,6 @@ import { headerFooterConfigStructs } from "../../header-footer/types";
import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
import { string, object, optional, assert } from "superstruct";
const cardConfigStruct = object({
type: string(),
@ -113,7 +114,9 @@ export class HuiEntityCardEditor extends LitElement
></ha-icon-input>
</div>
<div class="side-by-side">
<paper-input
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${this._entity}
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.attribute"
)} (${this.hass.localize(
@ -122,7 +125,7 @@ export class HuiEntityCardEditor extends LitElement
.value=${this._attribute}
.configValue=${"attribute"}
@value-changed=${this._valueChanged}
></paper-input>
></ha-entity-attribute-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.unit"

View File

@ -13,6 +13,7 @@ import { fireEvent } from "../../common/dom/fire_event";
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 { SelectMediaPlayerDialogParams } from "./show-select-media-source-dialog";
@customElement("hui-dialog-select-media-player")
@ -74,15 +75,18 @@ export class HuiDialogSelectMediaPlayer extends LitElement {
this.closeDialog();
}
static get styles(): CSSResult {
return css`
ha-dialog {
--dialog-content-padding: 0 24px 20px;
}
paper-item {
cursor: pointer;
}
`;
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-dialog {
--dialog-content-padding: 0 24px 20px;
}
paper-item {
cursor: pointer;
}
`,
];
}
}

View File

@ -11,6 +11,7 @@ import { fireEvent } from "../../common/dom/fire_event";
import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-hls-player";
import type { HomeAssistant } from "../../types";
import { haStyleDialog } from "../../resources/styles";
import { WebBrowserPlayMediaDialogParams } from "./show-media-player-dialog";
@customElement("hui-dialog-web-browser-play-media")
@ -92,26 +93,25 @@ export class HuiDialogWebBrowserPlayMedia extends LitElement {
`;
}
static get styles(): CSSResult {
return css`
ha-dialog {
--mdc-dialog-heading-ink-color: var(--primary-text-color);
}
@media (min-width: 800px) {
ha-dialog {
--mdc-dialog-max-width: 800px;
--mdc-dialog-min-width: 400px;
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
@media (min-width: 800px) {
ha-dialog {
--mdc-dialog-max-width: 800px;
--mdc-dialog-min-width: 400px;
}
}
}
video,
audio,
img {
outline: none;
width: 100%;
}
`;
video,
audio,
img {
outline: none;
width: 100%;
}
`,
];
}
}

View File

@ -0,0 +1,75 @@
import { css } from "lit-element";
export const sortableStyles = css`
#sortable a:nth-of-type(2n) paper-icon-item {
animation-name: keyframes1;
animation-iteration-count: infinite;
transform-origin: 50% 10%;
animation-delay: -0.75s;
animation-duration: 0.25s;
}
#sortable a:nth-of-type(2n-1) paper-icon-item {
animation-name: keyframes2;
animation-iteration-count: infinite;
animation-direction: alternate;
transform-origin: 30% 5%;
animation-delay: -0.5s;
animation-duration: 0.33s;
}
#sortable {
outline: none;
display: flex;
flex-direction: column;
}
.sortable-ghost {
opacity: 0.4;
}
.sortable-fallback {
opacity: 0;
}
@keyframes keyframes1 {
0% {
transform: rotate(-1deg);
animation-timing-function: ease-in;
}
50% {
transform: rotate(1.5deg);
animation-timing-function: ease-out;
}
}
@keyframes keyframes2 {
0% {
transform: rotate(1deg);
animation-timing-function: ease-in;
}
50% {
transform: rotate(-1.5deg);
animation-timing-function: ease-out;
}
}
.hide-panel {
display: none;
position: absolute;
right: 8px;
}
:host([expanded]) .hide-panel {
display: inline-flex;
}
paper-icon-item.hidden-panel,
paper-icon-item.hidden-panel span,
paper-icon-item.hidden-panel ha-icon[slot="item-icon"] {
color: var(--secondary-text-color);
cursor: pointer;
}
`;

View File

@ -88,9 +88,15 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
}
const themeMeta = document.querySelector("meta[name=theme-color]");
const headerColor = getComputedStyle(
document.documentElement
).getPropertyValue("--app-header-background-color");
const computedStyles = getComputedStyle(document.documentElement);
const headerColor = computedStyles.getPropertyValue(
"--app-header-background-color"
);
document.documentElement.style.backgroundColor = computedStyles.getPropertyValue(
"--primary-background-color"
);
if (themeMeta) {
if (!themeMeta.hasAttribute("default-content")) {
themeMeta.setAttribute(

View File

@ -1,14 +1,14 @@
/* eslint-disable no-console */
import { UpdatingElement } from "lit-element";
import { HASSDomEvent } from "../common/dom/fire_event";
import {
closeDialog,
showDialog,
DialogState,
DialogClosedParams,
DialogState,
showDialog,
} from "../dialogs/make-dialog-manager";
import { Constructor } from "../types";
import { HASSDomEvent } from "../common/dom/fire_event";
import { UpdatingElement } from "lit-element";
import { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
import { Constructor } from "../types";
const DEBUG = false;

View File

@ -9,6 +9,7 @@
"mailbox": "Mailbox",
"shopping_list": "Shopping list",
"developer_tools": "Developer Tools",
"media_browser": "Media Browser",
"profile": "Profile"
},
"state": {
@ -285,6 +286,10 @@
"entity": "Entity",
"clear": "Clear",
"show_entities": "Show entities"
},
"entity-attribute-picker": {
"attribute": "Attribute",
"show_attributes": "Show attributes"
}
},
"device-picker": {
@ -364,6 +369,7 @@
"audio_not_supported": "Your browser does not support the audio element.",
"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",
@ -913,6 +919,8 @@
"show_info_automation": "Show info about automation",
"delete_automation": "Delete automation",
"delete_confirm": "Are you sure you want to delete this automation?",
"duplicate_automation": "Duplicate automation",
"duplicate": "Duplicate",
"headers": {
"name": "Name"
}
@ -983,6 +991,7 @@
},
"state": {
"label": "State",
"attribute": "Attribute (Optional)",
"from": "From",
"for": "For",
"to": "To"
@ -1019,8 +1028,10 @@
"value_template": "Value template"
},
"time": {
"type_value": "Fixed time",
"type_input": "Value of a date/time helper",
"label": "Time",
"at": "At"
"at": "At time"
},
"time_pattern": {
"label": "Time Pattern",
@ -1096,6 +1107,8 @@
"value_template": "[%key:ui::panel::config::automation::editor::triggers::type::template::value_template%]"
},
"time": {
"type_value": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_value%]",
"type_input": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_input%]",
"label": "[%key:ui::panel::config::automation::editor::triggers::type::time::label%]",
"after": "After",
"before": "Before"
@ -1130,7 +1143,13 @@
"wait_template": {
"label": "Wait",
"wait_template": "Wait Template",
"timeout": "Timeout (optional)"
"timeout": "Timeout (optional)",
"continue_timeout": "Continue on timeout"
},
"wait_for_trigger": {
"label": "Wait for trigger",
"timeout": "[%key:ui::panel::config::automation::editor::actions::type::wait_template::timeout%]",
"continue_timeout": "[%key:ui::panel::config::automation::editor::actions::type::wait_template::continue_timeout%]"
},
"condition": {
"label": "Condition"
@ -1608,6 +1627,7 @@
"description": "Manage integrations",
"integration": "integration",
"discovered": "Discovered",
"attention": "Attention required",
"configured": "Configured",
"new": "Set up a new integration",
"add_integration": "Add integration",
@ -1616,6 +1636,7 @@
"note_about_website_reference": "More are available on the ",
"home_assistant_website": "Home Assistant website",
"configure": "Configure",
"reconfigure": "Reconfigure",
"none": "Nothing configured yet",
"none_found": "No integrations found",
"none_found_detail": "Adjust your search criteria.",
@ -1638,6 +1659,7 @@
"config_entry": {
"devices": "{count} {count, plural,\n one {device}\n other {devices}\n}",
"entities": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
"services": "{count} {count, plural,\n one {service}\n other {services}\n}",
"rename": "Rename",
"options": "Options",
"system_options": "System options",

View File

@ -505,6 +505,7 @@
"back": "Zpět",
"cancel": "Zrušit",
"close": "Zavřít",
"continue": "Pokračovat",
"delete": "Smazat",
"error_required": "Povinné",
"loading": "Načítání",
@ -562,6 +563,8 @@
"no_history_found": "Historie stavu chybí."
},
"media-browser": {
"audio_not_supported": "Váš prohlížeč nepodporuje element \"audio\".",
"choose_player": "Vyberte přehrávač",
"choose-source": "Zvolte zdroj",
"content-type": {
"album": "Album",
@ -570,12 +573,16 @@
"playlist": "Seznam skladeb",
"server": "Server"
},
"media_not_supported": "Přehrávač médií v prohlížeči nepodporuje tento typ média",
"media_player": "Přehrávač médií",
"media-player-browser": "Prohlížeč přehrávače médií",
"no_items": "Žádné položky",
"pick": "Vybrat",
"pick-media": "Vybrat média",
"play": "Přehrát",
"play-media": "Přehrát média"
"play-media": "Přehrát média",
"video_not_supported": "Váš prohlížeč nepodporuje element \"video\".",
"web-browser": "Webový prohlížeč"
},
"picture-upload": {
"label": "Obrázek",
@ -633,7 +640,7 @@
"icon": "Nahrazení ikony",
"icon_error": "Ikony by měly být ve formátu 'prefix:nazevikony', např. 'mdi:home'",
"name": "Přepsání názvu",
"note": "Poznámka: to nemusí fungovat se všemi integracemi.",
"note": "Poznámka: U všech integrací to ještě nemusí fungovat.",
"unavailable": "Tato entita není momentálně k dispozici.",
"update": "Aktualizovat"
},
@ -689,8 +696,10 @@
"crop": "Oříznout"
},
"more_info_control": {
"controls": "Ovládací prvky",
"dismiss": "Zavřít dialog",
"edit": "Upravit entitu",
"history": "Historie",
"person": {
"create_zone": "Vytvořit zónu z aktuálního umístění"
},
@ -1543,6 +1552,7 @@
"reload_restart_confirm": "Restartujte Home Assistant pro nové načtení této integrace",
"rename": "Přejmenovat",
"restart_confirm": "Restartujte Home Assistant pro odstranění této integrace",
"services": "{count} {count, plural,\n one {služba}\n few {služby}\n other {služeb}\n}",
"settings_button": "Upravit nastavení pro {integration}",
"system_options": "Více možností",
"system_options_button": "Upravit nastavení pro {integration}",
@ -1762,8 +1772,21 @@
"versions": "Získávám informace o verzích firmwaru a typech příkazů",
"wakeup": "Nastavuji podporu pro probouzecí fronty a zprávy"
},
"node": {
"button": "Podrobnosti uzlu",
"not_found": "Uzel nenalezen"
},
"nodes_table": {
"failed": "Selhalo",
"id": "ID",
"manufacturer": "Výrobce",
"model": "Model",
"query_stage": "Fáze dotazu",
"zwave_plus": "Z-Wave Plus"
},
"refresh_node": {
"battery_note": "Pokud je uzel napájen z baterie, nezapomeňte jej probudit, než budete pokračovat",
"button": "Obnovit uzel",
"complete": "Obnova uzlu dokončena",
"description": "Toto řekne OpenZWave, aby znovu provedl komunikaci s uzlem a aktualizoval typy příkazů, schopnosti a hodnoty uzlu.",
"node_status": "Stav uzlu",
@ -1912,7 +1935,7 @@
"filter": "Nově načíst entity integrace Filter",
"generic": "Nově načíst entity integrace Generic IP camera",
"generic_thermostat": "Nově načíst entity integrace Generic thermostat",
"group": "Nově načíst skupiny",
"group": "Nově načíst skupiny, skupiny entit a notifikační služby",
"heading": "Konfigurace se načítá",
"history_stats": "Nově načíst entity integrace History stats",
"homekit": "Nově načíst entity integrace HomeKit",
@ -1923,12 +1946,17 @@
"input_text": "Nově načíst pomocníky - texty",
"introduction": "Některé části Home Assistant lze nově načíst bez nutnosti restartování. Nové načtení zahodí jejich aktuální konfiguraci a načte novou.",
"min_max": "Nově načíst entity integrace Min/Max",
"mqtt": "Nově načíst entity integrace MQTT",
"person": "Nově načíst osoby",
"ping": "Nově načíst entity integrace Ping",
"rest": "Nově načíst entity integrace Rest",
"reload": "Nově načíst integraci {domain}",
"rest": "Nově načíst entity a notifikační služby integrace Rest",
"rpi_gpio": "Nově načíst entity integrace Raspberry Pi GPIO",
"scene": "Nově načíst scény",
"script": "Nově načíst skripty",
"smtp": "Nově načíst notifikační služby integrace SMTP",
"statistics": "Nově načíst entity integrace Statistics",
"telegram": "Nově načíst notifikační služby integrace Telegram",
"template": "Nově načíst entity integrace Template",
"trend": "Nově načíst entity integrace Trend",
"universal": "Nově načíst entity integrace Universal media player",
@ -2017,7 +2045,7 @@
"system": "Systémový"
}
},
"users_privileges_note": "Skupina uživatelů je v přípravě. Uživatel nebude moci spravovat instanci prostřednictvím uživatelského rozhraní. Stále kontrolujeme všechny koncové body API pro správu, abychom zajistili, že správně omezují přístup."
"users_privileges_note": "Skupiny uživatelů jsou v přípravě. Uživatel je nebude moci spravovat prostřednictvím uživatelského rozhraní. Stále kontrolujeme API pro správu, abychom zajistili, že správně omezuje přístup pouze pro administrátory."
},
"zha": {
"add_device_page": {
@ -2549,7 +2577,11 @@
}
},
"cardpicker": {
"by_card": "Podle karty",
"by_entity": "Podle entity",
"custom_card": "Vlastní",
"domain": "Doména",
"entity": "Entita",
"no_description": "Žádný popis není k dispozici."
},
"edit_card": {
@ -2563,6 +2595,7 @@
"options": "Více možností",
"pick_card": "Kterou kartu chcete přidat?",
"pick_card_view_title": "Kterou kartu byste chtěli přidat do svého {name} pohledu?",
"search_cards": "Vyhledat karty",
"show_code_editor": "Zobrazit editor kódu",
"show_visual_editor": "Zobrazit vizuální editor",
"toggle_editor": "Přepnout Editor",

View File

@ -19,6 +19,7 @@
"logbook": "Logbook",
"mailbox": "Mailbox",
"map": "Map",
"media_browser": "Media Browser",
"profile": "Profile",
"shopping_list": "Shopping list",
"states": "Overview"
@ -563,6 +564,8 @@
"no_history_found": "No state history found."
},
"media-browser": {
"audio_not_supported": "Your browser does not support the audio element.",
"choose_player": "Choose Player",
"choose-source": "Choose Source",
"content-type": {
"album": "Album",
@ -571,12 +574,17 @@
"playlist": "Playlist",
"server": "Server"
},
"media_browsing_error": "Media Browsing Error",
"media_not_supported": "The Browser Media Player does not support this type of media",
"media_player": "Media Player",
"media-player-browser": "Media Player Browser",
"no_items": "No items",
"pick": "Pick",
"pick-media": "Pick Media",
"play": "Play",
"play-media": "Play Media"
"play-media": "Play Media",
"video_not_supported": "Your browser does not support the video element.",
"web-browser": "Web Browser"
},
"picture-upload": {
"label": "Picture",
@ -634,7 +642,7 @@
"icon": "Icon Override",
"icon_error": "Icons should be in the format 'prefix:iconname', e.g. 'mdi:home'",
"name": "Name Override",
"note": "Note: this might not work yet with all integrations.",
"note": "Note: This might not work yet with all integrations.",
"unavailable": "This entity is not currently available.",
"update": "Update"
},
@ -1546,6 +1554,7 @@
"reload_restart_confirm": "Restart Home Assistant to finish reloading this integration",
"rename": "Rename",
"restart_confirm": "Restart Home Assistant to finish removing this integration",
"services": "{count} {count, plural,\n one {service}\n other {services}\n}",
"settings_button": "Edit settings for {integration}",
"system_options": "System options",
"system_options_button": "System options for {integration}",
@ -1765,8 +1774,21 @@
"versions": "Obtaining information about firmware and command class versions",
"wakeup": "Setting up support for wakeup queues and messages"
},
"node": {
"button": "Node Details",
"not_found": "Node not found"
},
"nodes_table": {
"failed": "Failed",
"id": "ID",
"manufacturer": "Manufacturer",
"model": "Model",
"query_stage": "Query Stage",
"zwave_plus": "Z-Wave Plus"
},
"refresh_node": {
"battery_note": "If the node is battery powered, be sure to wake it before proceeding",
"button": "Refresh Node",
"complete": "Node Refresh Complete",
"description": "This will tell OpenZWave to re-interview a node and update the node's command classes, capabilities, and values.",
"node_status": "Node Status",
@ -2025,7 +2047,7 @@
"system": "System"
}
},
"users_privileges_note": "The users group is a work in progress. The user will be unable to administer the instance via the UI. We're still auditing all management API endpoints to ensure that they correctly limit access to administrators."
"users_privileges_note": "The user group feature is a work in progress. The user will be unable to administer the instance via the UI. We're still auditing all management API endpoints to ensure that they correctly limit access to administrators."
},
"zha": {
"add_device_page": {
@ -2063,7 +2085,7 @@
"clusters": {
"header": "Clusters",
"help_cluster_dropdown": "Select a cluster to view attributes and commands.",
"introduction": "Clusters are the building blocks for Zigbee functionality. They seperate functionality into logical units. There are client and server types and that are comprised of attributes and commands."
"introduction": "Clusters are the building blocks for Zigbee functionality. They separate functionality into logical units. There are client and server types and that are comprised of attributes and commands."
},
"common": {
"add_devices": "Add Devices",
@ -2196,7 +2218,7 @@
"true": "True"
},
"node_management": {
"add_to_group": "Add To Group",
"add_to_group": "Add to Group",
"entities": "Entities of this node",
"entity_info": "Entity Information",
"exclude_entity": "Exclude this entity from Home Assistant",
@ -2209,11 +2231,11 @@
"node_to_control": "Node to control",
"nodes": "Nodes",
"nodes_hint": "Select node to view per-node options",
"nodes_in_group": "Other Nodes in this group:",
"nodes_in_group": "Other nodes in this group:",
"pooling_intensity": "Polling intensity",
"protection": "Protection",
"remove_broadcast": "Remove Broadcast",
"remove_from_group": "Remove From Group",
"remove_from_group": "Remove from Group",
"set_protection": "Set Protection"
},
"ozw_log": {
@ -2575,6 +2597,7 @@
"options": "More options",
"pick_card": "Which card would you like to add?",
"pick_card_view_title": "Which card would you like to add to your {name} view?",
"search_cards": "Search cards",
"show_code_editor": "Show Code Editor",
"show_visual_editor": "Show Visual Editor",
"toggle_editor": "Toggle Editor",

View File

@ -79,7 +79,7 @@
"default": {
"entity_not_found": "Entité introuvable",
"error": "Erreur",
"unavailable": "Indisponible",
"unavailable": "Indispo.",
"unknown": "Inconnu"
},
"device_tracker": {
@ -170,7 +170,7 @@
"on": "Problème"
},
"safety": {
"off": "Sécurisé",
"off": "Sûr",
"on": "Dangereux"
},
"smoke": {
@ -318,7 +318,7 @@
"fog": "Brouillard",
"hail": "Grêle",
"lightning": "Orage",
"lightning-rainy": "Orage / Pluie",
"lightning-rainy": "Orage / Pluvieux",
"partlycloudy": "Partiellement nuageux",
"pouring": "Averses",
"rainy": "Pluvieux",
@ -351,7 +351,7 @@
"alarm_control_panel": {
"arm_away": "Armer (absent)",
"arm_custom_bypass": "Bypass personnalisé",
"arm_home": "Armer (domicile)",
"arm_home": "Armer (présent)",
"arm_night": "Armer nuit",
"clear_code": "Effacer",
"code": "Code",
@ -505,6 +505,7 @@
"back": "Retour",
"cancel": "Annuler",
"close": "Fermer",
"continue": "Continuer",
"delete": "Supprimer",
"error_required": "Obligatoire",
"loading": "Chargement",
@ -562,6 +563,8 @@
"no_history_found": "Aucun historique des valeurs trouvé."
},
"media-browser": {
"audio_not_supported": "Votre navigateur ne prend pas en charge l'élément audio.",
"choose_player": "Choisissez le lecteur",
"choose-source": "Choisissez la source",
"content-type": {
"album": "Album",
@ -570,12 +573,16 @@
"playlist": "Liste de lecture",
"server": "Serveur"
},
"media_not_supported": "Le Browser Media Player ne prend pas en charge ce type de média",
"media_player": "Lecteur multimédia",
"media-player-browser": "Lecteur multimédia",
"no_items": "Aucun éléments",
"pick": "Choisir",
"pick-media": "Choisissez un média",
"play": "Lecture",
"play-media": "Lire le média"
"play-media": "Lire le média",
"video_not_supported": "Votre navigateur ne prend pas en charge l'élément vidéo.",
"web-browser": "Navigateur web"
},
"picture-upload": {
"label": "Image",
@ -689,8 +696,10 @@
"crop": "Recadrer"
},
"more_info_control": {
"controls": "Contrôles",
"dismiss": "Fermer la fenêtre de dialogue",
"edit": "Modifier l'entité",
"history": "Historique",
"person": {
"create_zone": "Créer une zone à partir de l'emplacement actuel"
},
@ -1442,7 +1451,7 @@
"name": "Nom",
"status": "Statut"
},
"introduction": "Home Assistant tient un registre de chaque entité qu'il a déjà vu au moins une fois et qui peut être identifié de manière unique. Chacune de ces entités se verra attribuer un identifiant qui sera réservé à cette seule entité.",
"introduction": "Home Assistant tient un registre de chaque entité qu'il a déjà vu au moins une fois et qui peut être identifiée de manière unique. Chacune de ces entités se verra attribuer un identifiant qui sera réservé à cette seule entité.",
"introduction2": "Utilisé le registre des entités pour remplacer le nom, modifier l'ID de l'entité ou supprimer l'entrée de Home Assistant.",
"remove_selected": {
"button": "Supprimer la sélection",
@ -1534,7 +1543,7 @@
"firmware": "Firmware: {version}",
"hub": "Connecté via",
"manuf": "par {manufacturer}",
"no_area": "Pas de pièce",
"no_area": "Pas de zone",
"no_device": "Entités sans appareils",
"no_devices": "Cette intégration n'a pas d'appareils.",
"options": "Options",
@ -1740,7 +1749,7 @@
},
"network": {
"header": "Gestion du réseau",
"introduction": "Διαχείρηση λειτουργιών δικτύου",
"introduction": "Gérez les fonctions de niveau réseau.",
"node_count": "{count} nœuds"
},
"node_query_stages": {
@ -1762,8 +1771,21 @@
"versions": "Obtention d'informations sur les versions des microprogrammes et des classes de commande",
"wakeup": "Configuration de la prise en charge des files d'attente et des messages de réveil"
},
"node": {
"button": "Détails du nœud",
"not_found": "Nœud introuvable"
},
"nodes_table": {
"failed": "Echec",
"id": "ID",
"manufacturer": "Fabricant",
"model": "Modèle",
"query_stage": "Étape de requête",
"zwave_plus": "Z-Wave Plus"
},
"refresh_node": {
"battery_note": "Si le nœud est alimenté par batterie, assurez-vous de l'activer avant de continuer",
"button": "Actualiser le nœud",
"complete": "Actualisation du nœud terminée",
"description": "Cela indiquera à OpenZWave de réinterroger le nœud et de le mettre à jour (commandes, possibilités et valeurs).",
"node_status": "État du nœud",
@ -1912,7 +1934,7 @@
"filter": "Recharger les entités de filtre",
"generic": "Recharger les entités de caméra IP générique",
"generic_thermostat": "Recharger les entités de thermostat générique",
"group": "Recharger les groupes",
"group": "Recharger les groupes, les entités de groupe et notifier les services",
"heading": "Rechargement de la configuration",
"history_stats": "Recharger les entités des statistiques historiques",
"homekit": "Recharger HomeKit",
@ -1923,12 +1945,17 @@
"input_text": "Recharger les entrées de texte (input text)",
"introduction": "Certaines parties de Home Assistant peuvent être rechargées sans nécessiter de redémarrage. Le fait de cliquer sur recharger déchargera leur configuration actuelle et chargera la nouvelle.",
"min_max": "Recharger les entités min/max",
"mqtt": "Recharger les entités mqtt",
"person": "Recharger les personnes",
"ping": "Recharger les entités de capteur binaire ping",
"rest": "Recharger les entités REST",
"reload": "Recharger {domain}",
"rest": "Recharger les entités REST et notifier les services",
"rpi_gpio": "Recharger les entités GPIO du Raspberry Pi",
"scene": "Recharger les scènes",
"script": "Recharger les scripts",
"smtp": "Recharger les services de notification smtp",
"statistics": "Recharger les entités de statistiques",
"telegram": "Recharger les services de notification de telegram",
"template": "Recharger les entités modèles",
"trend": "Recharger les entités de tendance",
"universal": "Recharger les entités de lecteur média universel",
@ -2017,7 +2044,7 @@
"system": "Système"
}
},
"users_privileges_note": "Le groupe d'utilisateurs est en cours de développement. L'utilisateur ne pourra pas gérer l'instance via l'interface. Nous vérifions les entrées de l'interface de gestion pour assurer que les accès soient limités aux administrateurs."
"users_privileges_note": "La fonctionnalité de groupe d'utilisateurs est en cours de développement. L'utilisateur ne pourra pas gérer l'instance via l'interface. Nous vérifions toujours tous les points terminaisons d'API pour assurer que les accès soient limités aux administrateurs."
},
"zha": {
"add_device_page": {
@ -2055,7 +2082,7 @@
"clusters": {
"header": "Clusters",
"help_cluster_dropdown": "Sélectionnez un cluster pour afficher les attributs et les commandes.",
"introduction": "Les clusters sont les blocs de construction de la fonctionnalité Zigbee. Ils séparent les fonctionnalités en unités logiques. Il existe des types de clients et de serveurs qui sont composés d'attributs et de commandes."
"introduction": "Les grappes sont les éléments de construction de la fonctionnalité Zigbee. Ils séparent les fonctionnalités en unités logiques. Il en existe de types client et serveur et sont composés d'attributs et de commandes."
},
"common": {
"add_devices": "Ajouter des appareils",
@ -2496,7 +2523,7 @@
},
"markdown": {
"content": "Contenu",
"description": "La carte Markdown est utilisée pour afficher du Markdown.",
"description": "La carte Markdown est utilisée pour le rendu du Markdown.",
"name": "Markdown"
},
"media-control": {
@ -2549,7 +2576,11 @@
}
},
"cardpicker": {
"by_card": "Par carte",
"by_entity": "Par entité",
"custom_card": "Personnalisé",
"domain": "Domaine",
"entity": "Entité",
"no_description": "Aucune description disponible."
},
"edit_card": {
@ -2563,6 +2594,7 @@
"options": "Plus d'options",
"pick_card": "Quelle carte aimeriez-vous ajouter ?",
"pick_card_view_title": "Quelle carte souhaitez-vous ajouter à votre vue {name} ?",
"search_cards": "Rechercher des cartes",
"show_code_editor": "Afficher l'éditeur de code",
"show_visual_editor": "Afficher l'éditeur visuel",
"toggle_editor": "Permuter léditeur",
@ -2765,7 +2797,7 @@
"data": {
"code": "Code d'authentification à deux facteurs"
},
"description": "Ouvrez le **{mfa_module_name}** sur votre appareil pour afficher votre code d'authentification à deux facteurs et vérifier votre identité:"
"description": "Ouvrez le ** {mfa_module_name} ** sur votre appareil pour afficher votre code d'authentification à deux facteurs et confirmer votre identité:"
}
}
},
@ -2785,7 +2817,7 @@
}
},
"start_over": "Recommencer",
"unknown_error": "Quelque chose a mal tourné",
"unknown_error": "Un problème est survenu",
"working": "Veuillez patienter"
},
"initializing": "Initialisation",

View File

@ -489,6 +489,7 @@
"back": "Tilbake",
"cancel": "Avbryt",
"close": "Lukk",
"continue": "Fortsette",
"delete": "Slett",
"error_required": "Nødvendig",
"loading": "Laster",
@ -672,8 +673,10 @@
"crop": "Beskjære"
},
"more_info_control": {
"controls": "Kontroller",
"dismiss": "Avvis dialogboksen",
"edit": "Redigér entitet",
"history": "Historie",
"person": {
"create_zone": "Opprett sone fra gjeldende plassering"
},
@ -1866,7 +1869,7 @@
"filter": "Last inn filter entiteter på nytt",
"generic": "Last inn generiske IP-kamera entiteter på nytt",
"generic_thermostat": "Last inn generiske termostat entiteter på nytt",
"group": "Last inn grupper på nytt",
"group": "Laste inn grupper, gruppere enheter og varsle tjenester på nytt",
"heading": "YAML -Konfigurasjon lastes på nytt",
"history_stats": "Last inn historiske tilstander på nytt",
"homekit": "Last inn HomeKit på nytt",
@ -1877,12 +1880,17 @@
"input_text": "Last inn inndata tekst på nytt",
"introduction": "Noen deler av Home Assistant kan laste inn uten å kreve omstart. Hvis du trykker last på nytt, vil du bytte den nåværende konfigurasjonen med den nye.",
"min_max": "Last inn min/maks entiteter på nytt",
"mqtt": "Last inn mqtt-enheter på nytt",
"person": "Last inn personer på nytt",
"ping": "Last inn ping binære sensor entiteter på nytt",
"rest": "Last inn REST entiteter på nytt",
"reload": "Last inn {domain} på nytt",
"rest": "Last inn hvileenheter på nytt og varsle tjenester",
"rpi_gpio": "Last inn Raspberry Pi GPIO-enheter på nytt",
"scene": "Last inn scener på nytt",
"script": "Last inn skript på nytt",
"smtp": "Last inn smtp-varslingstjenester på nytt",
"statistics": "Last inn statistiske entiteter på nytt",
"telegram": "Last inn telegram varslingstjenester på nytt",
"template": "Laste inn mal entiteter på nytt",
"trend": "Laste inn trend entiteter på nytt",
"universal": "Laste inn universelle mediespiller entiteter på nytt",
@ -2493,7 +2501,11 @@
}
},
"cardpicker": {
"by_card": "Med kort",
"by_entity": "Etter enhet",
"custom_card": "Tilpasset",
"domain": "Domene",
"entity": "Entitet",
"no_description": "Ingen beskrivelse tilgjengelig."
},
"edit_card": {

View File

@ -505,6 +505,7 @@
"back": "Wstecz",
"cancel": "Anuluj",
"close": "Zamknij",
"continue": "Kontynuuj",
"delete": "Usuń",
"error_required": "To pole jest wymagane",
"loading": "Ładowanie",
@ -562,6 +563,8 @@
"no_history_found": "Nie znaleziono historii."
},
"media-browser": {
"audio_not_supported": "Twoja przeglądarka nie obsługuje elementu audio.",
"choose_player": "Wybierz odtwarzacz",
"choose-source": "Wybierz źródło",
"content-type": {
"album": "Album",
@ -570,12 +573,16 @@
"playlist": "Lista odtwarzania",
"server": "Serwer"
},
"media_not_supported": "Przeglądarka odtwarzacza mediów nie obsługuje tego typu mediów",
"media_player": "Odtwarzacz mediów",
"media-player-browser": "Przeglądarka odtwarzacza mediów",
"no_items": "Brak elementów",
"pick": "Wybierz",
"pick-media": "Wybierz media",
"play": "Odtwarzaj",
"play-media": "Odtwarzaj media"
"play-media": "Odtwarzaj media",
"video_not_supported": "Twoja przeglądarka nie obsługuje elementu wideo.",
"web-browser": "Przeglądarka internetowa"
},
"picture-upload": {
"label": "Obraz",
@ -689,8 +696,10 @@
"crop": "Przytnij"
},
"more_info_control": {
"controls": "Sterowanie",
"dismiss": "Zamknij okno dialogowe",
"edit": "Edytuj encję",
"history": "Historia",
"person": {
"create_zone": "Utwórz strefę z bieżącej lokalizacji"
},
@ -1543,6 +1552,7 @@
"reload_restart_confirm": "Uruchom ponownie Home Assistanta, aby dokończyć ponowne wczytywanie tej integracji",
"rename": "Zmień nazwę",
"restart_confirm": "Zrestartuj Home Assistanta, aby zakończyć usuwanie tej integracji",
"services": "{count} {count, plural,\n one {usługa}\n few {usługi}\n many {usług}\n other {usług}\n}",
"settings_button": "Edytuj ustawienia dla {integration}",
"system_options": "Opcje systemowe",
"system_options_button": "Opcje systemowe dla {integration}",
@ -1746,7 +1756,7 @@
"node_query_stages": {
"associations": "Odświeżanie grup skojarzeń i członkostwa",
"cacheload": "Ładowanie informacji z pliku pamięci podręcznej OpenZWave. Węzły baterii pozostaną na tym etapie, dopóki węzeł się nie wybudzi.",
"complete": "Proces wywiadu jest zakończony",
"complete": "Proces odpytywania jest zakończony",
"configuration": "Pobieranie wartości konfiguracyjnych z węzła",
"dynamic": "Pobieranie często zmieniających się wartości z węzła",
"instances": "Pobieranie szczegółowych informacji o instancjach lub kanałach obsługiwanych przez urządzenie",
@ -1762,8 +1772,21 @@
"versions": "Pobieranie informacji o wersjach oprogramowania i klas poleceń",
"wakeup": "Konfigurowanie obsługi kolejek wybudzania i wiadomości"
},
"node": {
"button": "Szczegóły węzła",
"not_found": "Nie znaleziono węzła"
},
"nodes_table": {
"failed": "Uszkodzony",
"id": "Identyfikator",
"manufacturer": "Producent",
"model": "Model",
"query_stage": "Etap odpytywania",
"zwave_plus": "Z-Wave Plus"
},
"refresh_node": {
"battery_note": "Jeśli węzeł jest zasilany bateryjnie, przed kontynuowaniem należy go wybudzić",
"button": "Odśwież węzeł",
"complete": "Odświeżanie węzła zakończone",
"description": "Poinformuje to OpenZWave o konieczności ponownego odpytania węzła i zaktualizowaniu jego klas poleceń, możliwości i wartości.",
"node_status": "Stan węzła",
@ -1923,12 +1946,17 @@
"input_text": "Pomocnicy typu tekst",
"introduction": "Niektóre fragmenty konfiguracji można przeładować bez ponownego uruchamiania. Poniższe przyciski pozwalają na ponowne wczytanie danej części konfiguracji YAML.",
"min_max": "Encje komponentu min/max",
"mqtt": "Encje komponentu MQTT",
"person": "Osoby",
"ping": "Encje komponentu ping",
"reload": "Domenę {domain}",
"rest": "Encje komponentu rest",
"rpi_gpio": "Encje komponentu Raspberry Pi GPIO",
"scene": "Sceny",
"script": "Skrypty",
"smtp": "Usługi powiadomień komponentu SMTP",
"statistics": "Encje komponentu statystyka",
"telegram": "Usługi powiadomień komponentu Telegram",
"template": "Szablony encji",
"trend": "Encje komponentu trend",
"universal": "Encje komponentu uniwersalny odtwarzacz mediów",
@ -2549,7 +2577,11 @@
}
},
"cardpicker": {
"by_card": "Według karty",
"by_entity": "Według encji",
"custom_card": "Niestandardowa",
"domain": "Domena",
"entity": "Encja",
"no_description": "Brak dostępnego opisu."
},
"edit_card": {
@ -2563,6 +2595,7 @@
"options": "Więcej opcji",
"pick_card": "Wybierz kartę, którą chcesz dodać.",
"pick_card_view_title": "Którą kartę chcesz dodać do widoku {name}?",
"search_cards": "Szukaj kart",
"show_code_editor": "Edytor kodu",
"show_visual_editor": "Edytor wizualny",
"toggle_editor": "Przełącz edytor",

View File

@ -419,7 +419,7 @@
"unlock": "Desbloquear"
},
"media_player": {
"browse_media": "Pesquisar mídia",
"browse_media": "Pesquisar media",
"media_next_track": "Próximo",
"media_play": "Reproduzir",
"media_play_pause": "Reproduzir/pausar",
@ -505,6 +505,7 @@
"back": "Retroceder",
"cancel": "Cancelar",
"close": "Fechar",
"continue": "Continuar",
"delete": "Apagar",
"error_required": "Obrigatório",
"loading": "A carregar",
@ -562,6 +563,8 @@
"no_history_found": "Nenhum histórico de estado encontrado."
},
"media-browser": {
"audio_not_supported": "O seu navegador não suporta o elemento de áudio.",
"choose_player": "Escolha o Leitor",
"choose-source": "Escolha a fonte",
"content-type": {
"album": "Álbum",
@ -570,12 +573,15 @@
"playlist": "Lista de reprodução",
"server": "Servidor"
},
"media_player": "Leitor multimédia",
"media-player-browser": "Navegador do Media Player",
"no_items": "Sem itens",
"pick": "Escolher",
"pick-media": "Escolha a mídia",
"pick-media": "Escolha a média",
"play": "Reproduzir",
"play-media": "Reproduzir Mídia"
"play-media": "Reproduzir Média",
"video_not_supported": "O seu navegador não suporta o elemento de vídeo.",
"web-browser": "Navegador web"
},
"picture-upload": {
"label": "Imagem",
@ -600,7 +606,7 @@
"second": "{count} {count, plural,\n one {segundo}\n other {segundos}\n}",
"week": "{count} {count, plural,\n one {semana}\n other {semanas}\n}"
},
"future": "À {time}",
"future": " {time}",
"just_now": "Agora mesmo",
"never": "Nunca",
"past": "{time} atrás"
@ -690,6 +696,7 @@
"more_info_control": {
"dismiss": "Descartar diálogo",
"edit": "Editar entidade",
"history": "Histórico",
"person": {
"create_zone": "Criar zona a partir da localização atual"
},
@ -851,7 +858,7 @@
},
"automation": {
"caption": "Automação",
"description": "Criar e editar automações",
"description": "Gerir Automações",
"editor": {
"actions": {
"add": "Adicionar ação",
@ -1357,7 +1364,7 @@
"caption": "Dispositivos",
"confirm_delete": "Tem a certeza que quer apagar este dispositivo?",
"confirm_rename_entity_ids": "Deseja também renomear os id's de entidade de suas entidades?",
"confirm_rename_entity_ids_warning": "Isso não mudará nenhuma configuração (como automações, scripts, cenas, Lovelace) que está usando essas entidades, você mesmo terá que atualizá-las.",
"confirm_rename_entity_ids_warning": "Tal não altera nenhuma configuração (como automações, scripts, cenas, Lovelace) que esteja a usar essas entidades, terá que atualizá-las por si.",
"data_table": {
"area": "Área",
"battery": "Bateria",
@ -1720,10 +1727,22 @@
},
"network": {
"header": "Gestão de Rede",
"introduction": "Gerenciar funções em toda a rede.",
"introduction": "Gerir funções de rede.",
"node_count": "{count} nós"
},
"node": {
"button": "Detalhes do nó",
"not_found": "Nó não encontrado"
},
"nodes_table": {
"failed": "Falhou",
"id": "ID",
"manufacturer": "Fabricante",
"model": "Modelo",
"zwave_plus": "Z-Wave Plus"
},
"refresh_node": {
"button": "Atualizar nó",
"node_status": "Estado do Nó",
"refreshing_description": "A atualizar as informações do nó ...",
"start_refresh_button": "Iniciar atualização",
@ -1733,7 +1752,7 @@
},
"select_instance": {
"header": "Selecione uma instância OpenZWave",
"introduction": "Você tem mais de uma instância OpenZWave em execução. Qual instância você gostaria de gerenciar?"
"introduction": "Tem mais do que uma instância Openzwave em execução. Que instância deseja gerir?"
},
"services": {
"add_node": "Adicionar nó",
@ -1880,9 +1899,11 @@
"input_text": "Recarregar input texts",
"introduction": "Algumas partes do Home Assistant podem ser recarregadas sem a necessidade de reiniciar. Ao carregar em Recarregar a configuração irá descartar a configuração atual e carregar a nova.",
"min_max": "Recarregar entidades Mín. / Máx.",
"mqtt": "Recarregar entidades mqtt",
"person": "Recarregar pessoas",
"ping": "Recarregar entidades de sensor binárias de ping",
"rest": "Recarregar entidades REST",
"reload": "Recarregar {domain}",
"rest": "Recarregar as restantes entidades e notificar serviços",
"scene": "Recarregar cenas",
"script": "Recarregar scripts",
"statistics": "Recarregar entidades estatísticas",
@ -2011,7 +2032,7 @@
"clusters": {
"header": "Clusters",
"help_cluster_dropdown": "Selecione um cluster para visualizar atributos e comandos.",
"introduction": "Clusters são os blocos de construção para a funcionalidade Zigbee. Eles separam a funcionalidade em unidades lógicas. Existem tipos de cliente e servidor e que são compostos de atributos e comandos."
"introduction": "Os Clusters são os blocos de construção da funcionalidade Zigbee. Eles separam a funcionalidade em unidades lógicas. Existem do tipo cliente e servidor e são compostos de atributos e comandos."
},
"common": {
"add_devices": "Adicionar dispositivos",
@ -2505,7 +2526,11 @@
}
},
"cardpicker": {
"by_card": "Pelo Cartão",
"by_entity": "Pela Entidade",
"custom_card": "Personalizado",
"domain": "Domínio",
"entity": "Entidade",
"no_description": "Não há descrição disponível."
},
"edit_card": {
@ -2519,6 +2544,7 @@
"options": "Mais opções",
"pick_card": "Que cartão gostaria de adicionar?",
"pick_card_view_title": "Que cartão você gostaria de adicionar à sua vista {name}?",
"search_cards": "Procurar cartões",
"show_code_editor": "Mostrar Editor de Código",
"show_visual_editor": "Mostrar Editor Visual",
"toggle_editor": "Alternar Editor",
@ -2606,7 +2632,7 @@
},
"menu": {
"close": "Fechar",
"configure_ui": "Configurar UI",
"configure_ui": "Configurar Painel",
"exit_edit_mode": "Sair do modo de edição do IU",
"help": "Ajuda",
"refresh": "Atualizar",

View File

@ -505,6 +505,7 @@
"back": "Назад",
"cancel": "Отменить",
"close": "Закрыть",
"continue": "Продолжить",
"delete": "Удалить",
"error_required": "Обязательное поле",
"loading": "Загрузка",
@ -562,6 +563,7 @@
"no_history_found": "История не найдена."
},
"media-browser": {
"choose_player": "Выберите медиаплеер",
"choose-source": "Выбрать источник",
"content-type": {
"album": "Альбом",
@ -570,12 +572,14 @@
"playlist": "Плейлист",
"server": "Сервер"
},
"media_player": "Медиаплеер",
"media-player-browser": "Браузер медиаплеера",
"no_items": "Нет элементов",
"pick": "Выбрать",
"pick-media": "Выбрать Медиа",
"play": "Воспроизведение",
"play-media": "Воспроизведение Медиа"
"play-media": "Воспроизведение Медиа",
"web-browser": "Веб-браузер"
},
"picture-upload": {
"label": "Изображение",
@ -617,7 +621,7 @@
"update": "Обновить"
},
"domain_toggler": {
"reset_entities": "Сбросить объекты",
"reset_entities": "Сбросить настройки доступа объектов",
"title": "Переключить домены"
},
"entity_registry": {
@ -689,8 +693,10 @@
"crop": "Обрезать"
},
"more_info_control": {
"controls": "Управление",
"dismiss": "Закрыть диалог",
"edit": "Изменить объект",
"history": "История",
"person": {
"create_zone": "Создать зону из текущего местоположения"
},
@ -1201,15 +1207,19 @@
},
"alexa": {
"banner": "Редактирование списка доступных объектов через пользовательский интерфейс отключено, так как Вы уже настроили фильтры в файле configuration.yaml.",
"dont_expose_entity": "Закрыть доступ",
"expose": "Предоставить доступ",
"expose_entity": "Предоставить доступ к объекту",
"exposed_entities": "Объекты, к которым предоставлен доступ",
"expose_entity": "Открыть доступ",
"exposed": "Всего: {selected}",
"exposed_entities": "Объекты, к которым открыт доступ",
"follow_domain": "По домену",
"manage_domains": "Управление доменами",
"not_exposed_entities": "Объекты, к которым не предоставлен доступ",
"not_exposed": "Всего: {selected}",
"not_exposed_entities": "Объекты, к которым закрыт доступ",
"title": "Alexa"
},
"caption": "Home Assistant Cloud",
"description_features": "Управление сервером вдали от дома, интеграция с Alexa и Google Assistant",
"description_features": "Удалённый доступ к серверу, интеграция с Alexa и Google Assistant",
"description_login": "{email}",
"description_not_login": "Вход не выполнен",
"dialog_certificate": {
@ -1242,9 +1252,15 @@
"google": {
"banner": "Редактирование списка доступных объектов через пользовательский интерфейс отключено, так как Вы уже настроили фильтры в файле configuration.yaml.",
"disable_2FA": "Отключить двухфакторную аутентификацию",
"dont_expose_entity": "Закрыть доступ",
"expose": "Предоставить доступ",
"exposed_entities": "Объекты, к которым предоставлен доступ",
"not_exposed_entities": "Объекты, к которым не предоставлен доступ",
"expose_entity": "Открыть доступ",
"exposed": "Всего: {selected}",
"exposed_entities": "Объекты, к которым открыт доступ",
"follow_domain": "По домену",
"manage_domains": "Управление доменами",
"not_exposed": "Всего: {selected}",
"not_exposed_entities": "Объекты, к которым закрыт доступ",
"sync_to_google": "Синхронизация изменений с Google.",
"title": "Google Assistant"
},
@ -1255,7 +1271,7 @@
"email": "Адрес электронной почты",
"email_error_msg": "Неверный адрес электронной почты.",
"forgot_password": "забыли пароль?",
"introduction": "Home Assistant Cloud обеспечивает безопасный доступ к Вашему серверу, даже если Вы находитесь вдали от дома. Также это даёт возможность подключения к функциям облачных сервисов Amazon Alexa и Google Assistant.",
"introduction": "Home Assistant Cloud обеспечивает безопасный доступ к Вашему серверу, даже если Вы находитесь вдали от дома. Также это даёт возможность простого подключения к функциям облачных сервисов Amazon Alexa и Google Assistant.",
"introduction2": "Услуга предоставляется нашим партнером ",
"introduction2a": ", компанией от основателей Home Assistant и Hass.io.",
"introduction3": "Home Assistant Cloud предлагает одноразовый бесплатный пробный период продолжительностью один месяц. Для активации пробного периода платёжная информация не требуется.",
@ -1402,7 +1418,7 @@
},
"scripts": "Сценарии",
"unknown_error": "Неизвестная ошибка.",
"unnamed_device": "Безымянное устройство",
"unnamed_device": "Устройство без названия",
"update": "Обновить"
},
"entities": {
@ -1533,6 +1549,7 @@
"reload_restart_confirm": "Перезапустите Home Assistant, чтобы завершить перезагрузку этой интеграции",
"rename": "Переименовать",
"restart_confirm": "Перезапустите Home Assistant, чтобы завершить удаление этой интеграции",
"services": "{count} {count, plural,\n one {служба}\n other {служб}\n}",
"settings_button": "Настройки интеграции {integration}",
"system_options": "Настройки интеграции",
"system_options_button": "Системные параметры интеграции {integration}",
@ -1579,7 +1596,7 @@
"none_found_detail": "Измените критерии поиска",
"note_about_integrations": "Пока что не все интеграции могут быть настроены через пользовательский интерфейс.",
"note_about_website_reference": "Все доступные интеграции Вы можете найти на ",
"rename_dialog": "Изменение названия интеграции",
"rename_dialog": "Название интеграции",
"rename_input_label": "Название",
"search": "Поиск интеграций"
},
@ -1752,8 +1769,21 @@
"versions": "Получение информации о версиях прошивки и классов команд",
"wakeup": "Настройка поддержки очередей пробуждения и сообщений"
},
"node": {
"button": "Подробности об узле",
"not_found": "Узел не найден"
},
"nodes_table": {
"failed": "Сбой",
"id": "ID",
"manufacturer": "Производитель",
"model": "Модель",
"query_stage": "Стадия запроса",
"zwave_plus": "Z-Wave Plus"
},
"refresh_node": {
"battery_note": "Если узел работает от батареи, обязательно разбудите его, прежде чем продолжить",
"button": "Обновить узел",
"complete": "Обновление узла завершено",
"description": "Повторный опрос узла и обновление классов команд, возможностей и значений узла.",
"node_status": "Статус узла",
@ -1902,7 +1932,7 @@
"filter": "Перезагрузить объекты интеграции \"Filter\"",
"generic": "Перезагрузить объекты интеграции \"Generic IP Camera\"",
"generic_thermostat": "Перезагрузить объекты интеграции \"Generic Thermostat\"",
"group": "Перезагрузить группы",
"group": "Перезагрузить группы, объекты групп и службы уведомлений",
"heading": "Перезагрузка конфигурации YAML",
"history_stats": "Перезагрузить объекты интеграции \"History Stats\"",
"homekit": "Перезагрузить HomeKit",
@ -1913,12 +1943,17 @@
"input_text": "Перезагрузить вспомогательные элементы ввода текста",
"introduction": "Некоторые компоненты Home Assistant можно перезагрузить без необходимости перезапуска всей системы. Перезагрузка выгружает текущую конфигурацию YAML и загружает новую.",
"min_max": "Перезагрузить объекты интеграции \"Min/Max\"",
"mqtt": "Перезагрузить объекты интеграции \"MQTT\"",
"person": "Перезагрузить персоны",
"ping": "Перезагрузить объекты интеграции \"Ping (ICMP)\"",
"rest": "Перезагрузить объекты интеграции \"REST\"",
"reload": "Перезагрузить {domain}",
"rest": "Перезагрузить объекты и службы уведомлений интеграции \"REST\"",
"rpi_gpio": "Перезагрузить объекты интеграции \"Raspberry Pi GPIO\"",
"scene": "Перезагрузить сцены",
"script": "Перезагрузить сценарии",
"smtp": "Перезагрузить службы уведомлений SMTP",
"statistics": "Перезагрузить объекты интеграции \"Statistics\"",
"telegram": "Перезагрузить службы уведомлений Telegram",
"template": "Перезагрузить объекты шаблонов",
"trend": "Перезагрузить объекты интеграции \"Trend\"",
"universal": "Перезагрузить объекты интеграции \"Universal Media Player\"",
@ -2007,7 +2042,7 @@
"system": "Системный"
}
},
"users_privileges_note": "Группа пользователей находится в стадии разработки. В дальнейшем пользователи не смогут администрировать сервер через пользовательский интерфейс. Мы все еще проверяем все конечные точки API управления, чтобы убедиться, что они правильно ограничивают доступ."
"users_privileges_note": "Функционал пользователей всё ещё в стадии разработки. В дальнейшем пользователи не смогут администрировать сервер через пользовательский интерфейс. Мы все еще проверяем все конечные точки API управления, чтобы убедиться, что они правильно ограничивают доступ."
},
"zha": {
"add_device_page": {
@ -2539,7 +2574,11 @@
}
},
"cardpicker": {
"by_card": "Карточки",
"by_entity": "Объекты",
"custom_card": "Custom",
"domain": "Домен",
"entity": "Объект",
"no_description": "Описание недоступно."
},
"edit_card": {
@ -2553,6 +2592,7 @@
"options": "Больше параметров",
"pick_card": "Какую карточку Вы хотели бы добавить?",
"pick_card_view_title": "Какую карточку Вы хотели бы добавить на вкладку {name}?",
"search_cards": "Поиск карточек",
"show_code_editor": "Текстовый редактор",
"show_visual_editor": "Форма ввода",
"toggle_editor": "Переключить редактор",
@ -2838,7 +2878,7 @@
},
"integration": {
"finish": "Готово",
"intro": "Устройства и сервисы представлены в Home Assistant как интеграции. Вы можете добавить их сейчас или сделать это позже в разделе настроек.",
"intro": "Устройства и службы представлены в Home Assistant как интеграции. Вы можете добавить их сейчас или сделать это позже в разделе настроек.",
"more_integrations": "Ещё"
},
"intro": "Готовы ли Вы разбудить свой дом, вернуть свою конфиденциальность и присоединиться к всемирному сообществу?",

View File

@ -484,6 +484,7 @@
"back": "Späť",
"cancel": "Zrušiť",
"close": "Zavrieť",
"continue": "Pokračovať",
"delete": "Odstrániť",
"loading": "Načítava sa",
"next": "Ďalej",
@ -533,6 +534,11 @@
"loading_history": "Načítavam históriu stavov",
"no_history_found": "Nenašla sa žiadna história stavov"
},
"media-browser": {
"choose_player": "Vyberte prehrávač",
"media_player": "Prehrávač médií",
"web-browser": "Webový prehliadač"
},
"related-items": {
"area": "Oblasť",
"automation": "Súčasťou nasledujúcich automatizácií",
@ -635,6 +641,7 @@
"more_info_control": {
"dismiss": "Zrušiť dialógové okno",
"edit": "Upraviť entitu",
"history": "História",
"person": {
"create_zone": "Vytvoriť zónu z aktuálnej polohy"
},
@ -1534,6 +1541,11 @@
"title": "MQTT",
"topic": "téma"
},
"ozw": {
"nodes_table": {
"id": "ID"
}
},
"person": {
"add_person": "Pridať osobu",
"caption": "Osoby",
@ -1683,6 +1695,8 @@
"create": "Vytvoriť",
"name": "Meno",
"password": "Heslo",
"password_confirm": "Potvrdiť heslo",
"password_not_match": "Heslá sa nezhodujú",
"username": "Užívateľské meno"
},
"caption": "Používatelia",
@ -1699,7 +1713,9 @@
"group": "Skupina",
"id": "ID",
"name": "Názov",
"new_password": "Nové heslo",
"owner": "Vlastník",
"password_changed": "Heslo je zmenené!",
"system_generated": "Systémom vytvorený",
"system_generated_users_not_editable": "Nie je možné aktualizovať používateľov generovaných systémom.",
"system_generated_users_not_removable": "Nie je možné odstrániť používateľov generovaných systémom.",
@ -1872,6 +1888,10 @@
"set_wakeup": "Nastaviť interval prebudenia",
"true": "True"
},
"node_management": {
"add_to_group": "Pridať do skupiny",
"remove_from_group": "Odstrániť zo skupiny"
},
"ozw_log": {
"introduction": "Zobraziť denník. 0 je minimum (načíta celý protokol) a 1000 je maximum. Načítanie zobrazí statický protokol a posledný riadok sa automaticky aktualizuje s určeným počtom riadkov protokolu."
},
@ -2176,6 +2196,7 @@
"options": "Viac možností",
"pick_card": "Ktorú kartu chcete pridať?",
"pick_card_view_title": "Ktorú kartu chcete pridať do svojho zobrazenia {name} ?",
"search_cards": "Vyhľadať karty",
"show_code_editor": "Zobraziť editor kódu",
"show_visual_editor": "Zobraziť vizuálny editor",
"toggle_editor": "Prepnúť editor"

View File

@ -505,6 +505,7 @@
"back": "返回",
"cancel": "取消",
"close": "关闭",
"continue": "继续",
"delete": "删除",
"error_required": "必填",
"loading": "加载中",
@ -562,6 +563,8 @@
"no_history_found": "没有找到历史状态。"
},
"media-browser": {
"audio_not_supported": "您的浏览器不支持音频元素。",
"choose_player": "选择播放器",
"choose-source": "选择媒体源",
"content-type": {
"album": "专辑",
@ -570,12 +573,16 @@
"playlist": "播放列表",
"server": "服务器"
},
"media_not_supported": "浏览器媒体播放器不支持此类型的媒体",
"media_player": "媒体播放器",
"media-player-browser": "媒体播放浏览器",
"no_items": "没有项目",
"pick": "选定",
"pick-media": "选定媒体",
"play": "播放",
"play-media": "播放媒体"
"play-media": "播放媒体",
"video_not_supported": "您的浏览器不支持视频元素。",
"web-browser": "网页浏览器"
},
"picture-upload": {
"label": "图片",
@ -689,8 +696,10 @@
"crop": "剪裁"
},
"more_info_control": {
"controls": "控制项",
"dismiss": "关闭对话框",
"edit": "编辑实体",
"history": "历史",
"person": {
"create_zone": "从当前位置创建地点"
},
@ -1762,8 +1771,21 @@
"versions": "正在获取固件和命令类版本信息",
"wakeup": "正在设置对唤醒队列和消息的支持"
},
"node": {
"button": "节点详细信息",
"not_found": "节点未找到"
},
"nodes_table": {
"failed": "故障",
"id": "ID",
"manufacturer": "制造商",
"model": "型号",
"query_stage": "查询阶段",
"zwave_plus": "Z-Wave Plus"
},
"refresh_node": {
"battery_note": "如果节点由电池供电,请确保在继续操作之前将其唤醒",
"button": "刷新节点",
"complete": "节点刷新完成",
"description": "这将通知 OpenZWave 重新访问节点并更新节点的命令类、功能和值。",
"node_status": "节点状态",
@ -1912,7 +1934,7 @@
"filter": "重载 filter 实体",
"generic": "重载通用 IP 摄像机实体",
"generic_thermostat": "重载通用恒温器实体",
"group": "重载分组",
"group": "重载分组、分组实体及通知服务",
"heading": "配置重载",
"history_stats": "重载历史记录统计实体",
"homekit": "重载 HomeKit",
@ -1923,12 +1945,17 @@
"input_text": "重载文字输入",
"introduction": "Home Assistant 中的部分配置可以直接重载,而无需重启服务。点击重载按钮将重新载入新的配置。",
"min_max": "重载最小值/最大值实体",
"mqtt": "重载 mqtt 实体",
"person": "重载人员",
"ping": "重载 ping 二元传感器实体",
"rest": "重载 REST 实体",
"reload": "重载{domain}",
"rest": "重载 REST 实体及通知服务",
"rpi_gpio": "重载树莓派 GPIO 实体",
"scene": "重载场景",
"script": "重载脚本",
"smtp": "重载 smtp 通知服务",
"statistics": "重载 statistics 实体",
"telegram": "重载 telegram 通知服务",
"template": "重载模板实体",
"trend": "重载 trend 实体",
"universal": "重载通用媒体播放器实体",
@ -2549,7 +2576,11 @@
}
},
"cardpicker": {
"by_card": "按卡片",
"by_entity": "按实体",
"custom_card": "自定义",
"domain": "域",
"entity": "实体",
"no_description": "没有描述。"
},
"edit_card": {
@ -2563,6 +2594,7 @@
"options": "更多选项",
"pick_card": "请选择要添加的卡片。",
"pick_card_view_title": "您想将哪张卡片添加到 {name} 视图?",
"search_cards": "搜索卡片",
"show_code_editor": "显示代码编辑器",
"show_visual_editor": "显示可视化编辑器",
"toggle_editor": "切换编辑器",

View File

@ -505,6 +505,7 @@
"back": "上一步",
"cancel": "取消",
"close": "關閉",
"continue": "繼續",
"delete": "刪除",
"error_required": "必填",
"loading": "讀取中",
@ -562,6 +563,8 @@
"no_history_found": "找不到狀態歷史。"
},
"media-browser": {
"audio_not_supported": "瀏覽器不支援音效元件。",
"choose_player": "選擇播放器",
"choose-source": "選擇來源",
"content-type": {
"album": "專輯",
@ -570,12 +573,16 @@
"playlist": "播放列表",
"server": "伺服器"
},
"media_not_supported": "瀏覽器媒體播放器不支援此類型媒體",
"media_player": "媒體播放器",
"media-player-browser": "媒體播放器瀏覽器",
"no_items": "沒有項目",
"pick": "選擇",
"pick-media": "選擇媒體",
"play": "播放",
"play-media": "播放媒體"
"play-media": "播放媒體",
"video_not_supported": "瀏覽器不支援影片元件。",
"web-browser": "網頁瀏覽器"
},
"picture-upload": {
"label": "照片",
@ -689,8 +696,10 @@
"crop": "裁切"
},
"more_info_control": {
"controls": "控制",
"dismiss": "忽略對話",
"edit": "編輯實體",
"history": "歷史",
"person": {
"create_zone": "使用目前位置新增區域"
},
@ -1543,6 +1552,7 @@
"reload_restart_confirm": "重啟 Home Assistant 以為重整合重新載入",
"rename": "重新命名",
"restart_confirm": "重啟 Home Assistant 以完成此整合移動",
"services": "{count} {count, plural,\n one {項服務}\n other {項服務}\n}",
"settings_button": "編輯 {integration} 設定",
"system_options": "系統選項",
"system_options_button": "{integration} 系統選項",
@ -1762,8 +1772,21 @@
"versions": "獲得韌體與命令 Class 版本資訊",
"wakeup": "設定喚醒序列與訊息之支援"
},
"node": {
"button": "節點詳細資訊",
"not_found": "找不到節點"
},
"nodes_table": {
"failed": "失敗",
"id": "ID",
"manufacturer": "廠牌",
"model": "型號",
"query_stage": "查詢階段",
"zwave_plus": "Z-Wave Plus"
},
"refresh_node": {
"battery_note": "假如節點為電池供電、請確定先行喚醒以繼續",
"button": "更新節點",
"complete": "節點更新完成",
"description": "將會通知 OpenZWave 重新探訪節點並更新節點命令 Class、相容性與數值。",
"node_status": "節點狀態",
@ -1912,7 +1935,7 @@
"filter": "重新載入過濾器實體",
"generic": "重新載入通用 IP 攝影機實體",
"generic_thermostat": "重新載入通用溫控器實體",
"group": "重新載入群組",
"group": "重新載入群組、群組實體及通知服務",
"heading": "YAML 設定新載入中",
"history_stats": "重新載入歷史狀態實體",
"homekit": "重新載入 Homekit",
@ -1923,12 +1946,17 @@
"input_text": "重新載入輸入文字",
"introduction": "Home Assistant 中部分設定無須重啟即可重新載入生效。點選重新載入按鈕,即可解除目前 YAML 設定,並重新載入最新設定。",
"min_max": "重新載入最低/最高實體",
"mqtt": "重新載入 MQTT 實體",
"person": "重新載入人員",
"ping": "重新載入 Pung 二進位傳感器實體",
"rest": "重新載入剩餘實體",
"reload": "重新載入{domain}",
"rest": "重新載入剩餘實體及通知服務",
"rpi_gpio": "重新載入 Raspberry Pi GPIO 實體",
"scene": "重新載入場景",
"script": "重新載入腳本",
"smtp": "重新載入 SMTP 通知服務",
"statistics": "重新載入統計資訊實體",
"telegram": "重新載入 Telegram 通知服務",
"template": "重新載入範例實體",
"trend": "重新載入趨勢實體",
"universal": "重新載入通用媒體播放器實體",
@ -2017,7 +2045,7 @@
"system": "系統"
}
},
"users_privileges_note": "使用者群組功能進行中。將無法透過 UI 進行使用者管理,仍在檢視所有管理 API Endpoint 以確保能夠正確符合管理員存取需求。"
"users_privileges_note": "使用者群組功能仍在開發中。將無法透過 UI 進行使用者管理,仍在檢視所有管理 API Endpoint 以確保能夠正確符合管理員存取需求。"
},
"zha": {
"add_device_page": {
@ -2549,7 +2577,11 @@
}
},
"cardpicker": {
"by_card": "以面板",
"by_entity": "以實體",
"custom_card": "自訂面板",
"domain": "區域",
"entity": "實體",
"no_description": "無描述可使用。"
},
"edit_card": {
@ -2563,6 +2595,7 @@
"options": "更多選項",
"pick_card": "選擇所要新增的面板?",
"pick_card_view_title": "要加入 {name} 視圖的面板?",
"search_cards": "搜尋面板",
"show_code_editor": "顯示編碼編輯器",
"show_visual_editor": "顯示視覺編輯器",
"toggle_editor": "切換編輯器",

View File

@ -2710,6 +2710,11 @@
dependencies:
"@types/node" "*"
"@types/sortablejs@^1.10.6":
version "1.10.6"
resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.10.6.tgz#98725ae08f1dfe28b8da0fdf302c417f5ff043c0"
integrity sha512-QRz8Z+uw2Y4Gwrtxw8hD782zzuxxugdcq8X/FkPsXUa1kfslhGzy13+4HugO9FXNo+jlWVcE6DYmmegniIQ30A==
"@types/tern@*":
version "0.23.3"
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.3.tgz#4b54538f04a88c9ff79de1f6f94f575a7f339460"