mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-24 21:37:21 +00:00
Merge pull request #8448 from home-assistant/dev
Co-authored-by: Bram Kragten <mail@bramkragten.nl> Co-authored-by: Philip Allgaier <mail@spacegaier.de> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: GitHub Action <github-action@users.noreply.github.com> Co-authored-by: Joakim Sørensen <joasoe@gmail.com> Co-authored-by: Álvaro Fernández Rojas <noltari@gmail.com> Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Co-authored-by: Kendell R <KTibow@users.noreply.github.com> Co-authored-by: larena1 <60823161+larena1@users.noreply.github.com>
This commit is contained in:
commit
c85f69c9ee
@ -23,7 +23,7 @@ class HassioRouter extends HassRouterPage {
|
||||
protected routerOptions: RouterOptions = {
|
||||
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
||||
defaultPage: "dashboard",
|
||||
initialLoad: () => this._fetchData(),
|
||||
initialLoad: () => this._redirectIngress(),
|
||||
showLoading: true,
|
||||
routes: {
|
||||
dashboard: {
|
||||
@ -50,7 +50,13 @@ class HassioRouter extends HassRouterPage {
|
||||
|
||||
protected updatePageEl(el) {
|
||||
// the tabs page does its own routing so needs full route.
|
||||
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
||||
const hassioPanel = el.nodeName === "HASSIO-PANEL";
|
||||
const route = hassioPanel ? this.route : this.routeTail;
|
||||
|
||||
if (hassioPanel && this.panel.config?.ingress) {
|
||||
this._redirectIngress();
|
||||
return;
|
||||
}
|
||||
|
||||
el.hass = this.hass;
|
||||
el.narrow = this.narrow;
|
||||
@ -63,15 +69,14 @@ class HassioRouter extends HassRouterPage {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
private async _redirectIngress() {
|
||||
if (this.panel.config && this.panel.config.ingress) {
|
||||
this._redirectIngress(this.panel.config.ingress);
|
||||
this.route = {
|
||||
prefix: "/hassio",
|
||||
path: `/ingress/${this.panel.config.ingress}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _redirectIngress(addonSlug: string) {
|
||||
this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` };
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
20
package.json
20
package.json
@ -23,6 +23,14 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.0",
|
||||
"@codemirror/commands": "^0.17.2",
|
||||
"@codemirror/gutter": "^0.17.2",
|
||||
"@codemirror/highlight": "^0.17.2",
|
||||
"@codemirror/legacy-modes": "^0.17.1",
|
||||
"@codemirror/state": "^0.17.1",
|
||||
"@codemirror/stream-parser": "^0.17.1",
|
||||
"@codemirror/text": "^0.17.2",
|
||||
"@codemirror/view": "^0.17.7",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
||||
"@formatjs/intl-pluralrules": "^3.4.10",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
@ -177,7 +185,7 @@
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-typescript": "^7.2.1",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-import-resolver-webpack": "^0.12.2",
|
||||
"eslint-import-resolver-webpack": "^0.13.0",
|
||||
"eslint-plugin-disable": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-lit": "^1.2.0",
|
||||
@ -213,16 +221,16 @@
|
||||
"sinon": "^7.3.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^5.0.0",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "^4.0.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"webpack": "5.1.3",
|
||||
"webpack-cli": "4.1.0",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-manifest-plugin": "~3.0.0",
|
||||
"webpack": "^5.24.1",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-manifest-plugin": "^3.0.0",
|
||||
"workbox-build": "^5.1.3"
|
||||
},
|
||||
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210222.0",
|
||||
version="20210224.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -5,11 +5,15 @@ export const atLeastVersion = (
|
||||
patch?: number
|
||||
): boolean => {
|
||||
const [haMajor, haMinor, haPatch] = version.split(".", 3);
|
||||
|
||||
return (
|
||||
Number(haMajor) > major ||
|
||||
(Number(haMajor) === major && Number(haMinor) >= minor) ||
|
||||
(Number(haMajor) === major && (patch === undefined
|
||||
? Number(haMinor) >= minor
|
||||
: Number(haMinor) > minor)) ||
|
||||
(patch !== undefined &&
|
||||
Number(haMajor) === major && Number(haMinor) === minor &&
|
||||
Number(haMajor) === major &&
|
||||
Number(haMinor) === minor &&
|
||||
Number(haPatch) >= patch)
|
||||
);
|
||||
};
|
||||
|
@ -8,12 +8,19 @@ export const batteryIcon = (
|
||||
const battery = Number(batteryState.state);
|
||||
const battery_charging =
|
||||
batteryChargingState && batteryChargingState.state === "on";
|
||||
let icon = "hass:battery";
|
||||
|
||||
if (isNaN(battery)) {
|
||||
return "hass:battery-unknown";
|
||||
if (batteryState.state === "off") {
|
||||
icon += "-full";
|
||||
} else if (batteryState.state === "on") {
|
||||
icon += "-alert";
|
||||
} else {
|
||||
icon += "-unknown";
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
let icon = "hass:battery";
|
||||
const batteryRound = Math.round(battery / 10) * 10;
|
||||
if (battery_charging && battery > 10) {
|
||||
icon += `-charging-${batteryRound}`;
|
||||
|
@ -15,7 +15,7 @@ export const iconColorCSS = css`
|
||||
ha-icon[data-domain="media_player"][data-state="on"],
|
||||
ha-icon[data-domain="media_player"][data-state="paused"],
|
||||
ha-icon[data-domain="media_player"][data-state="playing"],
|
||||
ha-icon[data-domain="script"][data-state="running"],
|
||||
ha-icon[data-domain="script"][data-state="on"],
|
||||
ha-icon[data-domain="sun"][data-state="above_horizon"],
|
||||
ha-icon[data-domain="switch"][data-state="on"],
|
||||
ha-icon[data-domain="timer"][data-state="active"],
|
||||
|
@ -115,7 +115,7 @@ export class StateBadge extends LitElement {
|
||||
// eslint-disable-next-line
|
||||
console.warn(errorMessage);
|
||||
}
|
||||
// lowest brighntess will be around 50% (that's pretty dark)
|
||||
// lowest brightness will be around 50% (that's pretty dark)
|
||||
iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
|
||||
}
|
||||
}
|
||||
|
148
src/components/ha-addon-picker.ts
Normal file
148
src/components/ha-addon-picker.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { HassioAddonInfo } from "../data/hassio/addon";
|
||||
import { fetchHassioSupervisorInfo } from "../data/hassio/supervisor";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { HaComboBox } from "./ha-combo-box";
|
||||
|
||||
const rowRenderer = (
|
||||
root: HTMLElement,
|
||||
_owner,
|
||||
model: { item: HassioAddonInfo }
|
||||
) => {
|
||||
if (!root.firstElementChild) {
|
||||
root.innerHTML = `
|
||||
<style>
|
||||
paper-item {
|
||||
margin: -10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-item>
|
||||
<paper-item-body two-line="">
|
||||
<div class='name'>[[item.name]]</div>
|
||||
<div secondary>[[item.slug]]</div>
|
||||
</paper-item-body>
|
||||
</paper-item>
|
||||
`;
|
||||
}
|
||||
|
||||
root.querySelector(".name")!.textContent = model.item.name;
|
||||
root.querySelector("[secondary]")!.textContent = model.item.slug;
|
||||
};
|
||||
|
||||
@customElement("ha-addon-picker")
|
||||
class HaAddonPicker extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value = "";
|
||||
|
||||
@internalProperty() private _addons?: HassioAddonInfo[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@query("ha-combo-box") private _comboBox!: HaComboBox;
|
||||
|
||||
public open() {
|
||||
this._comboBox?.open();
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this._comboBox?.focus();
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._getAddons();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._addons) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.addon-picker.addon")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
.items=${this._addons}
|
||||
item-value-path="slug"
|
||||
item-id-path="slug"
|
||||
item-label-path="name"
|
||||
@value-changed=${this._addonChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _getAddons() {
|
||||
try {
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
this._addons = supervisorInfo.addons.sort((a, b) =>
|
||||
compare(a.name, b.name)
|
||||
);
|
||||
} else {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.componencts.addon-picker.error.no_supervisor.title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.componencts.addon-picker.error.no_supervisor.description"
|
||||
),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.componencts.addon-picker.error.fetch_addons.title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.componencts.addon-picker.error.fetch_addons.description"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
private _addonChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-addon-picker": HaAddonPicker;
|
||||
}
|
||||
}
|
@ -140,7 +140,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
this._entities = entities.filter((entity) => entity.area_id);
|
||||
}),
|
||||
];
|
||||
}
|
||||
@ -193,11 +193,14 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
inputDevices = devices;
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
} else if (deviceFilter) {
|
||||
inputDevices = devices;
|
||||
} else if (entityFilter) {
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
inputEntities = entities;
|
||||
} else {
|
||||
if (deviceFilter) {
|
||||
inputDevices = devices;
|
||||
}
|
||||
if (entityFilter) {
|
||||
inputEntities = entities;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeDomains) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Editor } from "codemirror";
|
||||
import type { StreamLanguage } from "@codemirror/stream-parser";
|
||||
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
|
||||
import {
|
||||
customElement,
|
||||
internalProperty,
|
||||
@ -15,32 +16,40 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const modeTag = Symbol("mode");
|
||||
|
||||
const readOnlyTag = Symbol("readOnly");
|
||||
|
||||
const saveKeyBinding: KeyBinding = {
|
||||
key: "Mod-s",
|
||||
run: (view: EditorView) => {
|
||||
fireEvent(view.dom, "editor-save");
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-code-editor")
|
||||
export class HaCodeEditor extends UpdatingElement {
|
||||
public codemirror?: Editor;
|
||||
public codemirror?: EditorView;
|
||||
|
||||
@property() public mode?: string;
|
||||
@property() public mode = "yaml";
|
||||
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public readOnly = false;
|
||||
|
||||
@property() public rtl = false;
|
||||
|
||||
@property() public error = false;
|
||||
|
||||
@internalProperty() private _value = "";
|
||||
|
||||
@internalProperty() private _langs?: Record<string, StreamLanguage<unknown>>;
|
||||
|
||||
public set value(value: string) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.codemirror ? this.codemirror.getValue() : this._value;
|
||||
}
|
||||
|
||||
public get hasComments(): boolean {
|
||||
return !!this.shadowRoot!.querySelector("span.cm-comment");
|
||||
return this.codemirror ? this.codemirror.state.doc.toString() : this._value;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
@ -48,7 +57,6 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
if (!this.codemirror) {
|
||||
return;
|
||||
}
|
||||
this.codemirror.refresh();
|
||||
if (this.autofocus !== false) {
|
||||
this.codemirror.focus();
|
||||
}
|
||||
@ -62,17 +70,27 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
}
|
||||
|
||||
if (changedProps.has("mode")) {
|
||||
this.codemirror.setOption("mode", this.mode);
|
||||
this.codemirror.dispatch({
|
||||
reconfigure: {
|
||||
[modeTag]: this._mode,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (changedProps.has("autofocus")) {
|
||||
this.codemirror.setOption("autofocus", this.autofocus !== false);
|
||||
if (changedProps.has("readOnly")) {
|
||||
this.codemirror.dispatch({
|
||||
reconfigure: {
|
||||
[readOnlyTag]: !this.readOnly,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (changedProps.has("_value") && this._value !== this.value) {
|
||||
this.codemirror.setValue(this._value);
|
||||
}
|
||||
if (changedProps.has("rtl")) {
|
||||
this.codemirror.setOption("gutters", this._calcGutters());
|
||||
this._setScrollBarDirection();
|
||||
this.codemirror.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: this.codemirror.state.doc.length,
|
||||
insert: this._value,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (changedProps.has("error")) {
|
||||
this.classList.toggle("error-state", this.error);
|
||||
@ -85,159 +103,62 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
this._load();
|
||||
}
|
||||
|
||||
private get _mode() {
|
||||
return this._langs![this.mode];
|
||||
}
|
||||
|
||||
private async _load(): Promise<void> {
|
||||
const loaded = await loadCodeMirror();
|
||||
|
||||
const codeMirror = loaded.codeMirror;
|
||||
this._langs = loaded.langs;
|
||||
|
||||
const shadowRoot = this.attachShadow({ mode: "open" });
|
||||
|
||||
shadowRoot!.innerHTML = `
|
||||
<style>
|
||||
${loaded.codeMirrorCss}
|
||||
.CodeMirror {
|
||||
height: var(--code-mirror-height, auto);
|
||||
direction: var(--code-mirror-direction, ltr);
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
max-height: var(--code-mirror-max-height, --code-mirror-height);
|
||||
}
|
||||
:host(.error-state) .CodeMirror-gutters {
|
||||
shadowRoot!.innerHTML = `<style>
|
||||
:host(.error-state) div.cm-wrap .cm-gutters {
|
||||
border-color: var(--error-state-color, red);
|
||||
}
|
||||
.CodeMirror-focused .CodeMirror-gutters {
|
||||
border-right: 2px solid var(--paper-input-container-focus-color, var(--primary-color));
|
||||
}
|
||||
.CodeMirror-linenumber {
|
||||
color: var(--paper-dialog-color, var(--secondary-text-color));
|
||||
}
|
||||
.rtl .CodeMirror-vscrollbar {
|
||||
right: auto;
|
||||
left: 0px;
|
||||
}
|
||||
.rtl-gutter {
|
||||
width: 20px;
|
||||
}
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid var(--paper-input-container-color, var(--secondary-text-color));
|
||||
background-color: var(--paper-dialog-background-color, var(--primary-background-color));
|
||||
transition: 0.2s ease border-right;
|
||||
}
|
||||
.cm-s-default.CodeMirror {
|
||||
background-color: var(--code-editor-background-color, var(--card-background-color));
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.cm-s-default .CodeMirror-cursor {
|
||||
border-left: 1px solid var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.cm-s-default div.CodeMirror-selected, .cm-s-default.CodeMirror-focused div.CodeMirror-selected {
|
||||
background: rgba(var(--rgb-primary-color), 0.2);
|
||||
}
|
||||
|
||||
.cm-s-default .CodeMirror-line::selection,
|
||||
.cm-s-default .CodeMirror-line>span::selection,
|
||||
.cm-s-default .CodeMirror-line>span>span::selection {
|
||||
background: rgba(var(--rgb-primary-color), 0.2);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-keyword {
|
||||
color: var(--codemirror-keyword, #6262FF);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-operator {
|
||||
color: var(--codemirror-operator, #cda869);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable-2 {
|
||||
color: var(--codemirror-variable-2, #690);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-builtin {
|
||||
color: var(--codemirror-builtin, #9B7536);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-atom {
|
||||
color: var(--codemirror-atom, #F90);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-number {
|
||||
color: var(--codemirror-number, #ca7841);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-def {
|
||||
color: var(--codemirror-def, #8DA6CE);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-string {
|
||||
color: var(--codemirror-string, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-string-2 {
|
||||
color: var(--codemirror-string-2, #bd6b18);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-comment {
|
||||
color: var(--codemirror-comment, #777);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable {
|
||||
color: var(--codemirror-variable, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-tag {
|
||||
color: var(--codemirror-tag, #997643);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-meta {
|
||||
color: var(--codemirror-meta, var(--primary-text-color));
|
||||
}
|
||||
|
||||
.cm-s-default .cm-attribute {
|
||||
color: var(--codemirror-attribute, #d6bb6d);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-property {
|
||||
color: var(--codemirror-property, #905);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-qualifier {
|
||||
color: var(--codemirror-qualifier, #690);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable-3 {
|
||||
color: var(--codemirror-variable-3, #07a);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-type {
|
||||
color: var(--codemirror-type, #07a);
|
||||
}
|
||||
</style>`;
|
||||
|
||||
this.codemirror = codeMirror(shadowRoot, {
|
||||
value: this._value,
|
||||
lineNumbers: true,
|
||||
tabSize: 2,
|
||||
mode: this.mode,
|
||||
autofocus: this.autofocus !== false,
|
||||
viewportMargin: Infinity,
|
||||
readOnly: this.readOnly,
|
||||
extraKeys: {
|
||||
Tab: "indentMore",
|
||||
"Shift-Tab": "indentLess",
|
||||
},
|
||||
gutters: this._calcGutters(),
|
||||
const container = document.createElement("span");
|
||||
|
||||
shadowRoot.appendChild(container);
|
||||
|
||||
this.codemirror = new loaded.EditorView({
|
||||
state: loaded.EditorState.create({
|
||||
doc: this._value,
|
||||
extensions: [
|
||||
loaded.lineNumbers(),
|
||||
loaded.keymap.of([
|
||||
...loaded.defaultKeymap,
|
||||
loaded.defaultTabBinding,
|
||||
saveKeyBinding,
|
||||
]),
|
||||
loaded.tagExtension(modeTag, this._mode),
|
||||
loaded.theme,
|
||||
loaded.Prec.fallback(loaded.highlightStyle),
|
||||
loaded.EditorView.updateListener.of((update) =>
|
||||
this._onUpdate(update)
|
||||
),
|
||||
loaded.tagExtension(
|
||||
readOnlyTag,
|
||||
loaded.EditorView.editable.of(!this.readOnly)
|
||||
),
|
||||
],
|
||||
}),
|
||||
root: shadowRoot,
|
||||
parent: container,
|
||||
});
|
||||
this._setScrollBarDirection();
|
||||
this.codemirror!.on("changes", () => this._onChange());
|
||||
}
|
||||
|
||||
private _blockKeyboardShortcuts() {
|
||||
this.addEventListener("keydown", (ev) => ev.stopPropagation());
|
||||
}
|
||||
|
||||
private _onChange(): void {
|
||||
private _onUpdate(update: ViewUpdate): void {
|
||||
if (!update.docChanged) {
|
||||
return;
|
||||
}
|
||||
const newValue = this.value;
|
||||
if (newValue === this._value) {
|
||||
return;
|
||||
@ -245,16 +166,6 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
this._value = newValue;
|
||||
fireEvent(this, "value-changed", { value: this._value });
|
||||
}
|
||||
|
||||
private _calcGutters(): string[] {
|
||||
return this.rtl ? ["rtl-gutter", "CodeMirror-linenumbers"] : [];
|
||||
}
|
||||
|
||||
private _setScrollBarDirection(): void {
|
||||
if (this.codemirror) {
|
||||
this.codemirror.getWrapperElement().classList.toggle("rtl", this.rtl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
30
src/components/ha-selector/ha-selector-addon.ts
Normal file
30
src/components/ha-selector/ha-selector-addon.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import { AddonSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-addon-picker";
|
||||
|
||||
@customElement("ha-selector-addon")
|
||||
export class HaAddonSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: AddonSelector;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-addon-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
allow-custom-entity
|
||||
></ha-addon-picker>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-addon": HaAddonSelector;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { Selector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-selector-action";
|
||||
import "./ha-selector-addon";
|
||||
import "./ha-selector-area";
|
||||
import "./ha-selector-boolean";
|
||||
import "./ha-selector-device";
|
||||
|
@ -79,6 +79,10 @@ class HaSlider extends PaperSliderClass {
|
||||
return subTemplate;
|
||||
}
|
||||
|
||||
_setImmediateValue(newImmediateValue) {
|
||||
super._setImmediateValue(Math.round(newImmediateValue));
|
||||
}
|
||||
|
||||
_calcStep(value) {
|
||||
if (!this.step) {
|
||||
return parseFloat(value);
|
||||
|
@ -5,20 +5,10 @@ import {
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { afterNextRender } from "../common/util/render-status";
|
||||
import "./ha-code-editor";
|
||||
import type { HaCodeEditor } from "./ha-code-editor";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"editor-refreshed": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const isEmpty = (obj: Record<string, unknown>): boolean => {
|
||||
if (typeof obj !== "object") {
|
||||
@ -44,8 +34,6 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
@internalProperty() private _yaml = "";
|
||||
|
||||
@query("ha-code-editor") private _editor?: HaCodeEditor;
|
||||
|
||||
public setValue(value): void {
|
||||
try {
|
||||
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
|
||||
@ -54,12 +42,6 @@ export class HaYamlEditor extends LitElement {
|
||||
console.error(err, value);
|
||||
alert(`There was an error converting to YAML: ${err}`);
|
||||
}
|
||||
afterNextRender(() => {
|
||||
if (this._editor?.codemirror) {
|
||||
this._editor.codemirror.refresh();
|
||||
}
|
||||
afterNextRender(() => fireEvent(this, "editor-refreshed"));
|
||||
});
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "./state-history-chart-line";
|
||||
@ -83,6 +84,10 @@ class StateHistoryCharts extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return !(changedProps.size === 1 && changedProps.has("hass"));
|
||||
}
|
||||
|
||||
private _isHistoryEmpty(): boolean {
|
||||
const historyDataEmpty =
|
||||
!this.historyData ||
|
||||
|
@ -9,6 +9,7 @@ export interface ConfigEntry {
|
||||
connection_class: string;
|
||||
supports_options: boolean;
|
||||
supports_unload: boolean;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
||||
export interface ConfigEntryMutableParams {
|
||||
@ -43,6 +44,27 @@ export const reloadConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
require_restart: boolean;
|
||||
}>("POST", `config/config_entries/entry/${configEntryId}/reload`);
|
||||
|
||||
export const disableConfigEntry = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string
|
||||
) =>
|
||||
hass.callWS<{
|
||||
require_restart: boolean;
|
||||
}>({
|
||||
type: "config_entries/disable",
|
||||
entry_id: configEntryId,
|
||||
disabled_by: "user",
|
||||
});
|
||||
|
||||
export const enableConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
hass.callWS<{
|
||||
require_restart: boolean;
|
||||
}>({
|
||||
type: "config_entries/disable",
|
||||
entry_id: configEntryId,
|
||||
disabled_by: null,
|
||||
});
|
||||
|
||||
export const getConfigEntrySystemOptions = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string
|
||||
|
@ -1,4 +1,5 @@
|
||||
export type Selector =
|
||||
| AddonSelector
|
||||
| EntitySelector
|
||||
| DeviceSelector
|
||||
| AreaSelector
|
||||
@ -30,6 +31,13 @@ export interface DeviceSelector {
|
||||
};
|
||||
}
|
||||
|
||||
export interface AddonSelector {
|
||||
addon: {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AreaSelector {
|
||||
area: {
|
||||
entity?: {
|
||||
|
@ -52,6 +52,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
caption="[[localize('ui.card.fan.speed')]]"
|
||||
min="0"
|
||||
max="100"
|
||||
step="[[computePercentageStepSize(stateObj)]]"
|
||||
value="{{percentageSliderValue}}"
|
||||
on-change="percentageChanged"
|
||||
pin=""
|
||||
@ -113,7 +114,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
<ha-attributes
|
||||
state-obj="[[stateObj]]"
|
||||
extra-filters="speed,preset_mode,preset_modes,speed_list,percentage,oscillating,direction"
|
||||
extra-filters="percentage_step,speed,preset_mode,preset_modes,speed_list,percentage,oscillating,direction"
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
@ -154,6 +155,13 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
}
|
||||
}
|
||||
|
||||
computePercentageStepSize(stateObj) {
|
||||
if (stateObj.attributes.percentage_step) {
|
||||
return stateObj.attributes.percentage_step;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
computeClassNames(stateObj) {
|
||||
return (
|
||||
"more-info-fan " +
|
||||
|
@ -12,13 +12,14 @@ import {
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-icon-next";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { ConfigEntry, disableConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
@ -160,6 +161,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
const batteryState = batteryEntity
|
||||
? this.hass.states[batteryEntity.entity_id]
|
||||
: undefined;
|
||||
const batteryIsBinary = batteryState
|
||||
&& computeStateDomain(batteryState) === "binary_sensor";
|
||||
const batteryChargingState = batteryChargingEntity
|
||||
? this.hass.states[batteryChargingEntity.entity_id]
|
||||
: undefined;
|
||||
@ -215,7 +218,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
batteryState
|
||||
? html`
|
||||
<div class="battery">
|
||||
${batteryState.state}%
|
||||
${batteryIsBinary ? "" : batteryState.state + "%"}
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${batteryState}
|
||||
@ -261,11 +264,13 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
${device.disabled_by === "user"
|
||||
? html` <div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>`
|
||||
: ""}
|
||||
`
|
||||
: html``
|
||||
}
|
||||
@ -626,6 +631,41 @@ export class HaConfigDevicePage extends LitElement {
|
||||
updateEntry: async (updates) => {
|
||||
const oldDeviceName = device.name_by_user || device.name;
|
||||
const newDeviceName = updates.name_by_user;
|
||||
const disabled =
|
||||
updates.disabled_by === "user" && device.disabled_by !== "user";
|
||||
|
||||
if (disabled) {
|
||||
for (const cnfg_entry of device.config_entries) {
|
||||
if (
|
||||
!this.devices.some(
|
||||
(dvc) =>
|
||||
dvc.id !== device.id &&
|
||||
dvc.config_entries.includes(cnfg_entry)
|
||||
)
|
||||
) {
|
||||
const config_entry = this.entries.find(
|
||||
(entry) => entry.entry_id === cnfg_entry
|
||||
);
|
||||
if (
|
||||
config_entry &&
|
||||
!config_entry.disabled_by &&
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
(await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.confirm_disable_config_entry",
|
||||
"entry_name",
|
||||
config_entry.title
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.yes"),
|
||||
dismissText: this.hass.localize("ui.common.no"),
|
||||
}))
|
||||
) {
|
||||
disableConfigEntry(this.hass, cnfg_entry);
|
||||
delete updates.disabled_by;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateDeviceRegistryEntry(this.hass, this.deviceId, updates);
|
||||
|
||||
if (
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
@ -293,9 +294,11 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
batteryEntityPair && batteryEntityPair[1]
|
||||
? this.hass.states[batteryEntityPair[1]]
|
||||
: undefined;
|
||||
return battery && !isNaN(battery.state as any)
|
||||
const batteryIsBinary =
|
||||
battery && computeStateDomain(battery) === "binary_sensor";
|
||||
return battery && (batteryIsBinary || !isNaN(battery.state as any))
|
||||
? html`
|
||||
${battery.state}%
|
||||
${batteryIsBinary ? "" : battery.state + "%"}
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${battery}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/app-route/app-route";
|
||||
@ -122,6 +123,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
|
||||
@internalProperty() private _showIgnored = false;
|
||||
|
||||
@internalProperty() private _showDisabled = false;
|
||||
|
||||
@internalProperty() private _searchParms = new URLSearchParams(
|
||||
window.location.hash.substring(1)
|
||||
);
|
||||
@ -181,18 +184,30 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
(
|
||||
configEntries: ConfigEntryExtended[],
|
||||
filter?: string
|
||||
): [Map<string, ConfigEntryExtended[]>, ConfigEntryExtended[]] => {
|
||||
): [
|
||||
Map<string, ConfigEntryExtended[]>,
|
||||
ConfigEntryExtended[],
|
||||
Map<string, ConfigEntryExtended[]>
|
||||
] => {
|
||||
const filteredConfigEnties = this._filterConfigEntries(
|
||||
configEntries,
|
||||
filter
|
||||
);
|
||||
const ignored: ConfigEntryExtended[] = [];
|
||||
const disabled: ConfigEntryExtended[] = [];
|
||||
for (let i = filteredConfigEnties.length - 1; i >= 0; i--) {
|
||||
if (filteredConfigEnties[i].source === "ignore") {
|
||||
ignored.push(filteredConfigEnties.splice(i, 1)[0]);
|
||||
}
|
||||
if (filteredConfigEnties[i].disabled_by !== null) {
|
||||
disabled.push(filteredConfigEnties.splice(i, 1)[0]);
|
||||
}
|
||||
}
|
||||
return [groupByIntegration(filteredConfigEnties), ignored];
|
||||
return [
|
||||
groupByIntegration(filteredConfigEnties),
|
||||
ignored,
|
||||
groupByIntegration(disabled),
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
@ -254,6 +269,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
const [
|
||||
groupedConfigEntries,
|
||||
ignoredConfigEntries,
|
||||
disabledConfigEntries,
|
||||
] = this._filterGroupConfigEntries(this._configEntries, this._filter);
|
||||
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
||||
this._configEntriesInProgress,
|
||||
@ -289,7 +305,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._toggleShowIgnored}
|
||||
@action=${this._handleMenuAction}
|
||||
>
|
||||
<mwc-icon-button
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
@ -305,6 +321,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
: "ui.panel.config.integrations.ignore.show_ignored"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
${this.hass.localize(
|
||||
this._showDisabled
|
||||
? "ui.panel.config.integrations.disable.hide_disabled"
|
||||
: "ui.panel.config.integrations.disable.show_disabled"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
|
||||
${!this.narrow
|
||||
@ -319,6 +342,20 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.integrations.search"
|
||||
)}
|
||||
></search-input>
|
||||
${!this._showDisabled && disabledConfigEntries.size
|
||||
? html`<div class="active-filters">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.disabled_integrations",
|
||||
"number",
|
||||
disabledConfigEntries.size
|
||||
)}
|
||||
<mwc-button @click=${this._toggleShowDisabled}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.filtering.show"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@ -433,6 +470,21 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
)
|
||||
: ""}
|
||||
${this._showDisabled
|
||||
? Array.from(disabledConfigEntries.entries()).map(
|
||||
([domain, items]) =>
|
||||
html`<ha-integration-card
|
||||
data-domain=${domain}
|
||||
disabled
|
||||
.hass=${this.hass}
|
||||
.domain=${domain}
|
||||
.items=${items}
|
||||
.manifest=${this._manifests[domain]}
|
||||
.entityRegistryEntries=${this._entityRegistryEntries}
|
||||
.deviceRegistryEntries=${this._deviceRegistryEntries}
|
||||
></ha-integration-card> `
|
||||
)
|
||||
: ""}
|
||||
${groupedConfigEntries.size
|
||||
? Array.from(groupedConfigEntries.entries()).map(
|
||||
([domain, items]) =>
|
||||
@ -596,10 +648,25 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
}
|
||||
|
||||
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._toggleShowIgnored();
|
||||
break;
|
||||
case 1:
|
||||
this._toggleShowDisabled();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleShowIgnored() {
|
||||
this._showIgnored = !this._showIgnored;
|
||||
}
|
||||
|
||||
private _toggleShowDisabled() {
|
||||
this._showDisabled = !this._showDisabled;
|
||||
}
|
||||
|
||||
private async _removeIgnoredIntegration(ev: Event) {
|
||||
const entry = (ev.target! as any).entry;
|
||||
showConfirmationDialog(this, {
|
||||
@ -767,11 +834,14 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
background: var(--sidebar-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
.search search-input {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
@ -796,6 +866,32 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
}
|
||||
.active-filters {
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 2px 2px 8px;
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.active-filters ha-icon {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.active-filters mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.active-filters::before {
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0.12;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
content: "";
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -9,12 +9,15 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import "../../../components/ha-icon-next";
|
||||
import {
|
||||
ConfigEntry,
|
||||
deleteConfigEntry,
|
||||
disableConfigEntry,
|
||||
enableConfigEntry,
|
||||
reloadConfigEntry,
|
||||
updateConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
@ -88,6 +91,8 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
@property() public selectedConfigEntryId?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
}
|
||||
@ -109,7 +114,14 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
private _renderGroupedIntegration(): TemplateResult {
|
||||
return html`
|
||||
<ha-card outlined class="group">
|
||||
<ha-card outlined class="group ${classMap({ disabled: this.disabled })}">
|
||||
${this.disabled
|
||||
? html`<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled"
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="group-header">
|
||||
<img
|
||||
src=${brandsUrl(this.domain, "icon")}
|
||||
@ -148,7 +160,9 @@ export class HaIntegrationCard extends LitElement {
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
class="single integration"
|
||||
class="single integration ${classMap({
|
||||
disabled: Boolean(item.disabled_by),
|
||||
})}"
|
||||
.configEntry=${item}
|
||||
.id=${item.entry_id}
|
||||
>
|
||||
@ -159,6 +173,17 @@ export class HaIntegrationCard extends LitElement {
|
||||
@click=${this._back}
|
||||
></ha-icon-button>`
|
||||
: ""}
|
||||
${item.disabled_by
|
||||
? html`<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled_cause",
|
||||
"cause",
|
||||
this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.disable.disabled_by.${item.disabled_by}`
|
||||
) || item.disabled_by
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
@ -222,11 +247,16 @@ export class HaIntegrationCard extends LitElement {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<div>
|
||||
<mwc-button @click=${this._editEntryName}
|
||||
>${this.hass.localize(
|
||||
${item.disabled_by === "user"
|
||||
? html`<mwc-button unelevated @click=${this._handleEnable}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
<mwc-button @click=${this._editEntryName}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}</mwc-button
|
||||
>
|
||||
)}
|
||||
</mwc-button>
|
||||
${item.domain in integrationsWithPanel
|
||||
? html`<a
|
||||
href=${`${
|
||||
@ -279,13 +309,25 @@ export class HaIntegrationCard extends LitElement {
|
||||
</mwc-list-item>
|
||||
</a>
|
||||
`}
|
||||
${item.state === "loaded" && item.supports_unload
|
||||
${!item.disabled_by &&
|
||||
item.state === "loaded" &&
|
||||
item.supports_unload
|
||||
? html`<mwc-list-item @request-selected="${this._handleReload}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.reload"
|
||||
)}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${item.disabled_by === "user"
|
||||
? html`<mwc-list-item @request-selected="${this._handleEnable}">
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-list-item>`
|
||||
: html`<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDisable}"
|
||||
>
|
||||
${this.hass.localize("ui.common.disable")}
|
||||
</mwc-list-item>`}
|
||||
<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDelete}"
|
||||
@ -370,6 +412,24 @@ export class HaIntegrationCard extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _handleDisable(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._disableIntegration(
|
||||
((ev.target as HTMLElement).closest("ha-card") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleEnable(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (ev.detail.source && !shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._enableIntegration(
|
||||
((ev.target as HTMLElement).closest("ha-card") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleSystemOptions(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
@ -385,6 +445,48 @@ export class HaIntegrationCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _disableIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disable_confirm"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
const result = await disableConfigEntry(this.hass, entryId);
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
fireEvent(this, "entry-updated", {
|
||||
entry: { ...configEntry, disabled_by: "user" },
|
||||
});
|
||||
}
|
||||
|
||||
private async _enableIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const result = await enableConfigEntry(this.hass, entryId);
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.enable_restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
fireEvent(this, "entry-updated", {
|
||||
entry: { ...configEntry, disabled_by: null },
|
||||
});
|
||||
}
|
||||
|
||||
private async _removeIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
@ -397,31 +499,29 @@ export class HaIntegrationCard extends LitElement {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
deleteConfigEntry(this.hass, entryId).then((result) => {
|
||||
fireEvent(this, "entry-removed", { entryId });
|
||||
const result = await deleteConfigEntry(this.hass, entryId);
|
||||
fireEvent(this, "entry-removed", { entryId });
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _reloadIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
reloadConfigEntry(this.hass, entryId).then((result) => {
|
||||
const locale_key = result.require_restart
|
||||
? "reload_restart_confirm"
|
||||
: "reload_confirm";
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.${locale_key}`
|
||||
),
|
||||
});
|
||||
const result = await reloadConfigEntry(this.hass, entryId);
|
||||
const locale_key = result.require_restart
|
||||
? "reload_restart_confirm"
|
||||
: "reload_confirm";
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.${locale_key}`
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@ -461,6 +561,15 @@ export class HaIntegrationCard extends LitElement {
|
||||
:host(.highlight) ha-card {
|
||||
border: 1px solid var(--accent-color);
|
||||
}
|
||||
.disabled {
|
||||
--ha-card-border-color: var(--warning-color);
|
||||
}
|
||||
.disabled .header {
|
||||
background: var(--warning-color);
|
||||
color: var(--text-primary-color);
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
|
@ -11,6 +11,7 @@ import memoizeOne from "memoize-one";
|
||||
import { LocalStorage } from "../../../common/decorators/local-storage";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-card";
|
||||
@ -40,7 +41,19 @@ class HaPanelDevService extends LitElement {
|
||||
|
||||
protected firstUpdated(params) {
|
||||
super.firstUpdated(params);
|
||||
if (!this._serviceData?.service) {
|
||||
const serviceParam = extractSearchParam("service");
|
||||
if (serviceParam) {
|
||||
this._serviceData = {
|
||||
service: serviceParam,
|
||||
target: {},
|
||||
data: {},
|
||||
};
|
||||
if (this._yamlMode) {
|
||||
this.updateComplete.then(() =>
|
||||
this._yamlEditor?.setValue(this._serviceData)
|
||||
);
|
||||
}
|
||||
} else if (!this._serviceData?.service) {
|
||||
const domain = Object.keys(this.hass.services).sort()[0];
|
||||
const service = Object.keys(this.hass.services[domain]).sort()[0];
|
||||
this._serviceData = {
|
||||
@ -48,6 +61,11 @@ class HaPanelDevService extends LitElement {
|
||||
target: {},
|
||||
data: {},
|
||||
};
|
||||
if (this._yamlMode) {
|
||||
this.updateComplete.then(() =>
|
||||
this._yamlEditor?.setValue(this._serviceData)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +158,10 @@ class HaPanelDevService extends LitElement {
|
||||
)}
|
||||
</th>
|
||||
</tr>
|
||||
${fields.map(
|
||||
${(this._yamlMode
|
||||
? fields
|
||||
: this._filterSelectorFields(fields)
|
||||
).map(
|
||||
(field) => html` <tr>
|
||||
<td><pre>${field.key}</pre></td>
|
||||
<td>${field.description}</td>
|
||||
|
@ -1,5 +1,8 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiInformationOutline } from "@mdi/js";
|
||||
import {
|
||||
mdiInformationOutline,
|
||||
mdiClipboardTextMultipleOutline
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
@ -15,6 +18,7 @@ import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
|
||||
const ERROR_SENTINEL = {};
|
||||
/*
|
||||
@ -165,7 +169,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
<th>[[localize('ui.panel.developer-tools.tabs.states.state')]]</th>
|
||||
<th hidden$="[[narrow]]">
|
||||
[[localize('ui.panel.developer-tools.tabs.states.attributes')]]
|
||||
<paper-checkbox checked="{{_showAttributes}}"></paper-checkbox>
|
||||
<paper-checkbox checked="{{_showAttributes}}" on-change="{{saveAttributeCheckboxState}}"></paper-checkbox>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -205,6 +209,12 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
title="[[localize('ui.panel.developer-tools.tabs.states.more_info')]]"
|
||||
path="[[informationOutlineIcon()]]"
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
on-click="copyEntity"
|
||||
alt="[[localize('ui.panel.developer-tools.tabs.states.copy_id')]]"
|
||||
title="[[localize('ui.panel.developer-tools.tabs.states.copy_id')]]"
|
||||
path="[[clipboardOutlineIcon()]]"
|
||||
></ha-svg-icon>
|
||||
<a href="#" on-click="entitySelected">[[entity.entity_id]]</a>
|
||||
</td>
|
||||
<td>
|
||||
@ -275,7 +285,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
|
||||
_showAttributes: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
value: JSON.parse(localStorage.getItem("devToolsShowAttributes") || true),
|
||||
},
|
||||
|
||||
_entities: {
|
||||
@ -296,6 +306,11 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
};
|
||||
}
|
||||
|
||||
copyEntity(ev) {
|
||||
ev.preventDefault();
|
||||
copyToClipboard(ev.model.entity.entity_id);
|
||||
}
|
||||
|
||||
entitySelected(ev) {
|
||||
const state = ev.model.entity;
|
||||
this._entityId = state.entity_id;
|
||||
@ -345,6 +360,10 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
return mdiInformationOutline;
|
||||
}
|
||||
|
||||
clipboardOutlineIcon() {
|
||||
return mdiClipboardTextMultipleOutline;
|
||||
}
|
||||
|
||||
computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter) {
|
||||
return Object.keys(hass.states)
|
||||
.map(function (key) {
|
||||
@ -459,6 +478,14 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
return Array.isArray(value) ? value.join(", ") : value;
|
||||
}
|
||||
|
||||
saveAttributeCheckboxState(ev) {
|
||||
try {
|
||||
localStorage.setItem("devToolsShowAttributes", ev.target.checked);
|
||||
} catch (e) {
|
||||
// Catch for Safari private mode
|
||||
}
|
||||
}
|
||||
|
||||
_computeParsedStateAttributes(stateAttributes) {
|
||||
try {
|
||||
return stateAttributes.trim() ? safeLoad(stateAttributes) : {};
|
||||
|
@ -49,10 +49,15 @@ export class HuiActionEditor extends LitElement {
|
||||
return config.url_path || "";
|
||||
}
|
||||
|
||||
get _service(): string {
|
||||
const config = this.config as CallServiceActionConfig;
|
||||
return config.service || "";
|
||||
}
|
||||
|
||||
private _serviceAction = memoizeOne(
|
||||
(config: CallServiceActionConfig): ServiceAction => {
|
||||
return {
|
||||
service: config.service || "",
|
||||
service: this._service,
|
||||
data: config.service_data,
|
||||
target: config.target,
|
||||
};
|
||||
@ -155,8 +160,25 @@ export class HuiActionEditor extends LitElement {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let data;
|
||||
switch (value) {
|
||||
case "url": {
|
||||
data = { url_path: this._url_path };
|
||||
break;
|
||||
}
|
||||
case "call-service": {
|
||||
data = { service: this._service };
|
||||
break;
|
||||
}
|
||||
case "navigate": {
|
||||
data = { navigation_path: this._navigation_path };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { action: value },
|
||||
value: { action: value, ...data },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -282,7 +282,7 @@ export class HuiDialogEditCard extends LitElement
|
||||
}
|
||||
|
||||
private _opened() {
|
||||
this._cardEditorEl?.refreshYamlEditor();
|
||||
this._cardEditorEl?.focusYamlEditor();
|
||||
}
|
||||
|
||||
private get _canSave(): boolean {
|
||||
|
@ -61,8 +61,8 @@ export class HuiConditionalCardEditor extends LitElement
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public refreshYamlEditor(focus) {
|
||||
this._cardEditorEl?.refreshYamlEditor(focus);
|
||||
public focusYamlEditor() {
|
||||
this._cardEditorEl?.focusYamlEditor();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
@ -54,8 +54,8 @@ export class HuiStackCardEditor extends LitElement
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public refreshYamlEditor(focus) {
|
||||
this._cardEditorEl?.refreshYamlEditor(focus);
|
||||
public focusYamlEditor() {
|
||||
this._cardEditorEl?.focusYamlEditor();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
@ -157,17 +157,14 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
this.GUImode = !this.GUImode;
|
||||
}
|
||||
|
||||
public refreshYamlEditor(focus = false) {
|
||||
if (this._configElement?.refreshYamlEditor) {
|
||||
this._configElement.refreshYamlEditor(focus);
|
||||
public focusYamlEditor() {
|
||||
if (this._configElement?.focusYamlEditor) {
|
||||
this._configElement.focusYamlEditor();
|
||||
}
|
||||
if (!this._yamlEditor?.codemirror) {
|
||||
return;
|
||||
}
|
||||
this._yamlEditor.codemirror.refresh();
|
||||
if (focus) {
|
||||
this._yamlEditor.codemirror.focus();
|
||||
}
|
||||
this._yamlEditor.codemirror.focus();
|
||||
}
|
||||
|
||||
protected async getConfigElement(): Promise<
|
||||
@ -290,7 +287,7 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
|
||||
if (this._configElementType !== this.configElementType) {
|
||||
// If the type has changed, we need to load a new GUI editor
|
||||
this._guiSupported = false;
|
||||
this._guiSupported = undefined;
|
||||
this._configElement = undefined;
|
||||
|
||||
if (!this.configElementType) {
|
||||
|
@ -120,13 +120,13 @@ const actionConfigStructConfirmation = union([
|
||||
|
||||
const actionConfigStructUrl = object({
|
||||
action: literal("url"),
|
||||
url_path: optional(string()),
|
||||
url_path: string(),
|
||||
confirmation: optional(actionConfigStructConfirmation),
|
||||
});
|
||||
|
||||
const actionConfigStructService = object({
|
||||
action: literal("call-service"),
|
||||
service: optional(string()),
|
||||
service: string(),
|
||||
service_data: optional(object()),
|
||||
target: optional(
|
||||
object({
|
||||
@ -140,7 +140,7 @@ const actionConfigStructService = object({
|
||||
|
||||
const actionConfigStructNavigate = object({
|
||||
action: literal("navigate"),
|
||||
navigation_path: optional(string()),
|
||||
navigation_path: string(),
|
||||
confirmation: optional(actionConfigStructConfirmation),
|
||||
});
|
||||
|
||||
|
@ -54,7 +54,8 @@ class HuiScriptEntityRow extends LitElement implements LovelaceRow {
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
${stateObj.state === "on"
|
||||
? html`<mwc-button @click=${this._cancelScript}>
|
||||
${(stateObj.attributes.current || 0) > 0
|
||||
${stateObj.attributes.mode !== "single" &&
|
||||
(stateObj.attributes.current || 0) > 0
|
||||
? this.hass.localize(
|
||||
"ui.card.script.cancel_multiple",
|
||||
"number",
|
||||
|
@ -29,6 +29,8 @@ export interface WeblinkConfig {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
url: string;
|
||||
new_tab?: boolean;
|
||||
download?: boolean;
|
||||
}
|
||||
export interface TextConfig {
|
||||
type: "text";
|
||||
|
@ -47,8 +47,6 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
|
||||
@internalProperty() private _changed?: boolean;
|
||||
|
||||
private _generation = 1;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-app-layout>
|
||||
@ -133,11 +131,7 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
}
|
||||
|
||||
.content {
|
||||
height: calc(100vh - 68px);
|
||||
}
|
||||
|
||||
hui-code-editor {
|
||||
height: 100%;
|
||||
height: calc(100vh - var(--header-height));
|
||||
}
|
||||
|
||||
.save-button {
|
||||
@ -154,15 +148,11 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _yamlChanged() {
|
||||
this._changed = !this.yamlEditor
|
||||
.codemirror!.getDoc()
|
||||
.isClean(this._generation);
|
||||
if (this._changed && !window.onbeforeunload) {
|
||||
this._changed = true;
|
||||
if (!window.onbeforeunload) {
|
||||
window.onbeforeunload = () => {
|
||||
return true;
|
||||
};
|
||||
} else if (!this._changed && window.onbeforeunload) {
|
||||
window.onbeforeunload = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +214,7 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.yamlEditor.hasComments) {
|
||||
if (/^#|\s#/gm.test(value)) {
|
||||
if (
|
||||
!confirm(
|
||||
this.hass.localize(
|
||||
@ -281,9 +271,6 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
),
|
||||
});
|
||||
}
|
||||
this._generation = this.yamlEditor
|
||||
.codemirror!.getDoc()
|
||||
.changeGeneration(true);
|
||||
window.onbeforeunload = null;
|
||||
this._saving = false;
|
||||
this._changed = false;
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import "../../../components/ha-icon";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceRow, WeblinkConfig } from "../entity-rows/types";
|
||||
@ -37,8 +38,9 @@ class HuiWeblinkRow extends LitElement implements LovelaceRow {
|
||||
return html`
|
||||
<a
|
||||
href=${this._config.url}
|
||||
target=${this._config.url.indexOf("://") !== -1 ? "_blank" : ""}
|
||||
target=${ifDefined(this._computeTargetValue())}
|
||||
rel="noreferrer"
|
||||
?download=${this._config.download}
|
||||
>
|
||||
<ha-icon .icon="${this._config.icon}"></ha-icon>
|
||||
<div>${this._config.name}</div>
|
||||
@ -66,6 +68,15 @@ class HuiWeblinkRow extends LitElement implements LovelaceRow {
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
protected _computeTargetValue(): string | undefined {
|
||||
return this._config &&
|
||||
(this._config.url.indexOf("://") !== -1 ||
|
||||
this._config.new_tab === true ||
|
||||
this._config.download === true)
|
||||
? "_blank"
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -86,5 +86,5 @@ export interface LovelaceGenericElementEditor extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
lovelace?: LovelaceConfig;
|
||||
setConfig(config: any): void;
|
||||
refreshYamlEditor?: (focus: boolean) => void;
|
||||
focusYamlEditor?: () => void;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
import "../../layouts/hass-error-screen";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
|
||||
const REDIRECTS: Redirects = {
|
||||
developer_states: {
|
||||
@ -23,12 +24,21 @@ const REDIRECTS: Redirects = {
|
||||
developer_services: {
|
||||
redirect: "/developer-tools/service",
|
||||
},
|
||||
developer_call_service: {
|
||||
redirect: "/developer-tools/service",
|
||||
params: {
|
||||
service: "string",
|
||||
},
|
||||
},
|
||||
developer_template: {
|
||||
redirect: "/developer-tools/template",
|
||||
},
|
||||
developer_events: {
|
||||
redirect: "/developer-tools/event",
|
||||
},
|
||||
config: {
|
||||
redirect: "/config",
|
||||
},
|
||||
cloud: {
|
||||
component: "cloud",
|
||||
redirect: "/config/cloud",
|
||||
@ -42,6 +52,18 @@ const REDIRECTS: Redirects = {
|
||||
domain: "string",
|
||||
},
|
||||
},
|
||||
config_mqtt: {
|
||||
component: "mqtt",
|
||||
redirect: "/config/mqtt",
|
||||
},
|
||||
config_zha: {
|
||||
component: "zha",
|
||||
redirect: "/config/zha/dashboard",
|
||||
},
|
||||
config_zwave_js: {
|
||||
component: "zwave_js",
|
||||
redirect: "/config/zwave_js/dashboard",
|
||||
},
|
||||
devices: {
|
||||
redirect: "/config/devices/dashboard",
|
||||
},
|
||||
@ -52,39 +74,49 @@ const REDIRECTS: Redirects = {
|
||||
redirect: "/config/areas/dashboard",
|
||||
},
|
||||
blueprints: {
|
||||
component: "blueprint",
|
||||
redirect: "/config/blueprint/dashboard",
|
||||
},
|
||||
blueprint_import: {
|
||||
component: "blueprint",
|
||||
redirect: "/config/blueprint/dashboard/import",
|
||||
params: {
|
||||
blueprint_url: "url",
|
||||
},
|
||||
},
|
||||
automations: {
|
||||
component: "automation",
|
||||
redirect: "/config/automation/dashboard",
|
||||
},
|
||||
scenes: {
|
||||
component: "scene",
|
||||
redirect: "/config/scene/dashboard",
|
||||
},
|
||||
scripts: {
|
||||
component: "script",
|
||||
redirect: "/config/script/dashboard",
|
||||
},
|
||||
helpers: {
|
||||
redirect: "/config/helpers",
|
||||
},
|
||||
tags: {
|
||||
component: "tags",
|
||||
redirect: "/config/tags",
|
||||
},
|
||||
lovelace_dashboards: {
|
||||
component: "lovelace",
|
||||
redirect: "/config/lovelace/dashboards",
|
||||
},
|
||||
lovelace_resources: {
|
||||
component: "lovelace",
|
||||
redirect: "/config/lovelace/resources",
|
||||
},
|
||||
people: {
|
||||
component: "person",
|
||||
redirect: "/config/person",
|
||||
},
|
||||
zones: {
|
||||
component: "zone",
|
||||
redirect: "/config/zone",
|
||||
},
|
||||
users: {
|
||||
@ -108,6 +140,14 @@ const REDIRECTS: Redirects = {
|
||||
profile: {
|
||||
redirect: "/profile/dashboard",
|
||||
},
|
||||
logbook: {
|
||||
component: "logbook",
|
||||
redirect: "/logbook",
|
||||
},
|
||||
history: {
|
||||
component: "history",
|
||||
redirect: "/history",
|
||||
},
|
||||
};
|
||||
|
||||
export type ParamType = "url" | "string";
|
||||
@ -201,12 +241,16 @@ class HaPanelMy extends LitElement {
|
||||
) || "This redirect is not supported.";
|
||||
break;
|
||||
case "no_supervisor":
|
||||
error =
|
||||
this.hass.localize(
|
||||
"ui.panel.my.component_not_loaded",
|
||||
"integration",
|
||||
"Home Assistant Supervisor"
|
||||
) || "This redirect requires Home Assistant Supervisor.";
|
||||
error = this.hass.localize(
|
||||
"ui.panel.my.no_supervisor",
|
||||
"docs_link",
|
||||
html`<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="${documentationUrl(this.hass, "/installation")}"
|
||||
>${this.hass.localize("ui.panel.my.documentation")}</a
|
||||
>`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
error = this.hass.localize("ui.panel.my.error") || "Unknown error";
|
||||
|
@ -1,11 +1,8 @@
|
||||
interface LoadedCodeMirror {
|
||||
codeMirror: any;
|
||||
codeMirrorCss: any;
|
||||
}
|
||||
let loaded: Promise<typeof import("./codemirror")>;
|
||||
|
||||
let loaded: Promise<LoadedCodeMirror>;
|
||||
|
||||
export const loadCodeMirror = async (): Promise<LoadedCodeMirror> => {
|
||||
export const loadCodeMirror = async (): Promise<
|
||||
typeof import("./codemirror")
|
||||
> => {
|
||||
if (!loaded) {
|
||||
loaded = import("./codemirror");
|
||||
}
|
||||
|
@ -1,14 +1,122 @@
|
||||
// @ts-ignore
|
||||
import _CodeMirror, { Editor } from "codemirror";
|
||||
// @ts-ignore
|
||||
import _codeMirrorCss from "codemirror/lib/codemirror.css";
|
||||
import "codemirror/mode/jinja2/jinja2";
|
||||
import "codemirror/mode/yaml/yaml";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HighlightStyle, tags } from "@codemirror/highlight";
|
||||
import { EditorView as CMEditorView } from "@codemirror/view";
|
||||
import { StreamLanguage } from "@codemirror/stream-parser";
|
||||
import { jinja2 } from "@codemirror/legacy-modes/mode/jinja2";
|
||||
import { yaml } from "@codemirror/legacy-modes/mode/yaml";
|
||||
|
||||
// @ts-ignore
|
||||
_CodeMirror.commands.save = (cm: Editor) => {
|
||||
fireEvent(cm.getWrapperElement(), "editor-save");
|
||||
export { keymap } from "@codemirror/view";
|
||||
export { CMEditorView as EditorView };
|
||||
export { EditorState, Prec, tagExtension } from "@codemirror/state";
|
||||
export { defaultKeymap, defaultTabBinding } from "@codemirror/commands";
|
||||
export { lineNumbers } from "@codemirror/gutter";
|
||||
|
||||
export const langs = {
|
||||
jinja2: StreamLanguage.define(jinja2),
|
||||
yaml: StreamLanguage.define(yaml),
|
||||
};
|
||||
export const codeMirror: any = _CodeMirror;
|
||||
export const codeMirrorCss: any = _codeMirrorCss;
|
||||
|
||||
export const theme = CMEditorView.theme({
|
||||
$: {
|
||||
color: "var(--primary-text-color)",
|
||||
backgroundColor:
|
||||
"var(--code-editor-background-color, var(--card-background-color))",
|
||||
"& ::selection": { backgroundColor: "rgba(var(--rgb-primary-color), 0.2)" },
|
||||
caretColor: "var(--secondary-text-color)",
|
||||
height: "var(--code-mirror-height, auto)",
|
||||
},
|
||||
|
||||
$$focused: { outline: "none" },
|
||||
|
||||
"$$focused $cursor": { borderLeftColor: "#var(--secondary-text-color)" },
|
||||
"$$focused $selectionBackground, $selectionBackground": {
|
||||
backgroundColor: "rgba(var(--rgb-primary-color), 0.2)",
|
||||
},
|
||||
|
||||
$gutters: {
|
||||
backgroundColor:
|
||||
"var(--paper-dialog-background-color, var(--primary-background-color))",
|
||||
color: "var(--paper-dialog-color, var(--secondary-text-color))",
|
||||
border: "none",
|
||||
borderRight:
|
||||
"1px solid var(--paper-input-container-color, var(--secondary-text-color))",
|
||||
},
|
||||
"$$focused $gutters": {
|
||||
borderRight:
|
||||
"2px solid var(--paper-input-container-focus-color, var(--primary-color))",
|
||||
},
|
||||
"$gutterElementags.lineNumber": { color: "inherit" },
|
||||
});
|
||||
|
||||
export const highlightStyle = HighlightStyle.define(
|
||||
{ tag: tags.keyword, color: "var(--codemirror-keyword, #6262FF)" },
|
||||
{
|
||||
tag: [
|
||||
tags.name,
|
||||
tags.deleted,
|
||||
tags.character,
|
||||
tags.propertyName,
|
||||
tags.macroName,
|
||||
],
|
||||
color: "var(--codemirror-property, #905)",
|
||||
},
|
||||
{
|
||||
tag: [tags.function(tags.variableName), tags.labelName],
|
||||
color: "var(--codemirror-variable, #07a)",
|
||||
},
|
||||
{
|
||||
tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)],
|
||||
color: "var(--codemirror-qualifier, #690)",
|
||||
},
|
||||
{
|
||||
tag: [tags.definition(tags.name), tags.separator],
|
||||
color: "var(--codemirror-def, #8DA6CE)",
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
tags.typeName,
|
||||
tags.className,
|
||||
tags.number,
|
||||
tags.changed,
|
||||
tags.annotation,
|
||||
tags.modifier,
|
||||
tags.self,
|
||||
tags.namespace,
|
||||
],
|
||||
color: "var(--codemirror-number, #ca7841)",
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
tags.operator,
|
||||
tags.operatorKeyword,
|
||||
tags.url,
|
||||
tags.escape,
|
||||
tags.regexp,
|
||||
tags.link,
|
||||
tags.special(tags.string),
|
||||
],
|
||||
color: "var(--codemirror-operator, #cda869)",
|
||||
},
|
||||
{ tag: tags.comment, color: "var(--codemirror-comment, #777)" },
|
||||
{
|
||||
tag: tags.meta,
|
||||
color: "var(--codemirror-meta, var(--primary-text-color))",
|
||||
},
|
||||
{ tag: tags.strong, fontWeight: "bold" },
|
||||
{ tag: tags.emphasis, fontStyle: "italic" },
|
||||
{
|
||||
tag: tags.link,
|
||||
color: "var(--primary-color)",
|
||||
textDecoration: "underline",
|
||||
},
|
||||
{ tag: tags.heading, fontWeight: "bold" },
|
||||
{ tag: tags.atom, color: "var(--codemirror-atom, #F90)" },
|
||||
{ tag: tags.bool, color: "var(--codemirror-atom, #F90)" },
|
||||
{
|
||||
tag: tags.special(tags.variableName),
|
||||
color: "var(--codemirror-variable-2, #690)",
|
||||
},
|
||||
{ tag: tags.processingInstruction, color: "var(--secondary-text-color)" },
|
||||
{ tag: tags.string, color: "var(--codemirror-string, #07a)" },
|
||||
{ tag: tags.inserted, color: "var(--codemirror-string2, #07a)" },
|
||||
{ tag: tags.invalid, color: "var(--error-color)" }
|
||||
);
|
||||
|
@ -33,7 +33,8 @@ export class StateCardScript extends LitElement {
|
||||
></state-info>
|
||||
${stateObj.state === "on"
|
||||
? html`<mwc-button @click=${this._cancelScript}>
|
||||
${(stateObj.attributes.current || 0) > 0
|
||||
${stateObj.attributes.mode !== "single" &&
|
||||
(stateObj.attributes.current || 0) > 0
|
||||
? this.hass.localize(
|
||||
"ui.card.script.cancel_multiple",
|
||||
"number",
|
||||
|
@ -98,7 +98,7 @@
|
||||
"disabled_by": {
|
||||
"user": "User",
|
||||
"integration": "Integration",
|
||||
"config_entry": "Config Entry",
|
||||
"config_entry": "Config entry",
|
||||
"device": "Device"
|
||||
}
|
||||
},
|
||||
@ -339,8 +339,8 @@
|
||||
}
|
||||
},
|
||||
"target-picker": {
|
||||
"expand_area_id": "Expand this area in the seperate devices and entities that it contains. After expanding it will not update the devices and entities when the area changes.",
|
||||
"expand_device_id": "Expand this device in seperate entities. After expanding it will not update the entities when the device changes.",
|
||||
"expand_area_id": "Expand this area into the separate devices and entities that it contains. After expanding, it will not update the devices and entities when the area changes.",
|
||||
"expand_device_id": "Expand this device into the separate entities that it contains. After expanding, it will not update the entities when the device changes.",
|
||||
"remove_area_id": "Remove area",
|
||||
"remove_device_id": "Remove device",
|
||||
"remove_entity_id": "Remove entity",
|
||||
@ -382,6 +382,19 @@
|
||||
"failed_create_area": "Failed to create area."
|
||||
}
|
||||
},
|
||||
"addon-picker": {
|
||||
"addon": "Add-on",
|
||||
"error": {
|
||||
"no_supervisor": {
|
||||
"title": "No Supervisor",
|
||||
"description": "No Supervisor found, so add-ons could not be loaded."
|
||||
},
|
||||
"fetch_addons": {
|
||||
"title": "Error fetching add-ons",
|
||||
"description": "Fetching add-ons returned an error."
|
||||
}
|
||||
}
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Picture",
|
||||
"unsupported_format": "Unsupported format, please choose a JPEG, PNG or GIF image."
|
||||
@ -426,8 +439,8 @@
|
||||
},
|
||||
"service-control": {
|
||||
"required": "This field is required",
|
||||
"target": "Target",
|
||||
"target_description": "What should this service call target",
|
||||
"target": "Targets",
|
||||
"target_description": "What should this service use as targeted areas, devices or entities.",
|
||||
"service_data": "Service data"
|
||||
},
|
||||
"related-items": {
|
||||
@ -813,6 +826,8 @@
|
||||
"my": {
|
||||
"not_supported": "This redirect is not supported by your Home Assistant instance. Check the {link} for the supported redirects and the version they where introduced.",
|
||||
"component_not_loaded": "This redirect is not supported by your Home Assistant instance. You need the integration {integration} to use this redirect.",
|
||||
"no_supervisor": "This redirect is not supported by your Home Assistant installation. It needs either the Home Assistant Operating System or Home Assistant Supervised installation method. For more information, see the {docs_link}.",
|
||||
"documentation": "documentation",
|
||||
"faq_link": "My Home Assistant FAQ",
|
||||
"error": "An unknown error occured"
|
||||
},
|
||||
@ -821,7 +836,8 @@
|
||||
"introduction": "In this view it is possible to configure your components and Home Assistant. Not everything is possible to configure from the UI yet, but we're working on it.",
|
||||
"filtering": {
|
||||
"filtering_by": "Filtering by",
|
||||
"clear": "Clear"
|
||||
"clear": "Clear",
|
||||
"show": "Show"
|
||||
},
|
||||
"advanced_mode": {
|
||||
"hint_enable": "Missing config options? Enable advanced mode on",
|
||||
@ -1819,7 +1835,7 @@
|
||||
"disabled_by": {
|
||||
"user": "User",
|
||||
"integration": "Integration",
|
||||
"config_entry": "Config Entry"
|
||||
"config_entry": "Config entry"
|
||||
},
|
||||
"enabled_description": "Disabled devices will not be shown and entities belonging to the device will be disabled and not added to Home Assistant.",
|
||||
"automation": {
|
||||
@ -1870,6 +1886,7 @@
|
||||
"scenes": "Scenes",
|
||||
"confirm_rename_entity_ids": "Do you also want to rename the entity IDs of your entities?",
|
||||
"confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, dashboards) that is currently using these entities! You will have to update them yourself to use the new entity IDs!",
|
||||
"confirm_disable_config_entry": "There are no more devices for the config entry {entry_name}, do you want to instead disable the config entry?",
|
||||
"disabled": "Disabled",
|
||||
"data_table": {
|
||||
"device": "Device",
|
||||
@ -2027,6 +2044,11 @@
|
||||
"rename_dialog": "Edit the name of this config entry",
|
||||
"rename_input_label": "Entry name",
|
||||
"search": "Search integrations",
|
||||
"disable": {
|
||||
"show_disabled": "Show disabled integrations",
|
||||
"hide_disabled": "Hide disabled integrations",
|
||||
"disabled_integrations": "{number} disabled"
|
||||
},
|
||||
"ignore": {
|
||||
"ignore": "Ignore",
|
||||
"confirm_ignore_title": "Ignore discovery of {name}?",
|
||||
@ -2052,6 +2074,8 @@
|
||||
"restart_confirm": "Restart Home Assistant to finish removing this integration",
|
||||
"reload_confirm": "The integration was reloaded",
|
||||
"reload_restart_confirm": "Restart Home Assistant to finish reloading this integration",
|
||||
"disable_restart_confirm": "Restart Home Assistant to finish disabling this integration",
|
||||
"enable_restart_confirm": "Restart Home Assistant to finish enabling this integration",
|
||||
"manuf": "by {manufacturer}",
|
||||
"hub": "Connected via",
|
||||
"firmware": "Firmware: {version}",
|
||||
@ -2059,7 +2083,17 @@
|
||||
"device_unavailable": "Device unavailable",
|
||||
"entity_unavailable": "Entity unavailable",
|
||||
"area": "In {area}",
|
||||
"no_area": "No Area"
|
||||
"no_area": "No Area",
|
||||
"disable": {
|
||||
"disabled": "Disabled",
|
||||
"disabled_cause": "Disabled by {cause}",
|
||||
"disabled_by": {
|
||||
"user": "user",
|
||||
"integration": "integration",
|
||||
"device": "device"
|
||||
},
|
||||
"disable_confirm": "Are you sure you want to disable this config entry? It's devices and entities will be disabled."
|
||||
}
|
||||
},
|
||||
"config_flow": {
|
||||
"aborted": "Aborted",
|
||||
@ -2620,7 +2654,7 @@
|
||||
"confirm_remove_config_title": "Are you sure you want to remove your Lovelace UI configuration?",
|
||||
"confirm_remove_config_text": "We will automatically generate your Lovelace UI views with your areas and devices if you remove your Lovelace UI configuration.",
|
||||
"confirm_unsaved_changes": "You have unsaved changes, are you sure you want to exit?",
|
||||
"confirm_unsaved_comments": "Your configuration contains comment(s), these will not be saved. Do you want to continue?",
|
||||
"confirm_unsaved_comments": "Your configuration might contains comment(s), these will not be saved. Do you want to continue?",
|
||||
"error_parse_yaml": "Unable to parse YAML: {error}",
|
||||
"error_invalid_config": "Your configuration is not valid: {error}",
|
||||
"error_save_yaml": "Unable to save YAML: {error}",
|
||||
@ -3311,7 +3345,8 @@
|
||||
"more_info": "More Info",
|
||||
"alert_entity_field": "Entity is a mandatory field",
|
||||
"last_updated": "[%key:ui::dialogs::more_info_control::last_updated%]",
|
||||
"last_changed": "[%key:ui::dialogs::more_info_control::last_changed%]"
|
||||
"last_changed": "[%key:ui::dialogs::more_info_control::last_changed%]",
|
||||
"copy_id": "Copy ID to clipboard"
|
||||
},
|
||||
"templates": {
|
||||
"title": "Template",
|
||||
|
41
test-mocha/common/config/version.ts
Normal file
41
test-mocha/common/config/version.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { assert } from "chai";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
|
||||
const testTruthyData = [
|
||||
{ version: "2021.1.1", major: 2021, minor: 1, patch: 1 },
|
||||
{ version: "2021.1.1", major: 2021, minor: 1 },
|
||||
{ version: "2021.1.1", major: 2020, minor: 12, patch: 1 },
|
||||
{ version: "2021.1.1", major: 2020, minor: 12 },
|
||||
{ version: "2021.1.1", major: 2021, minor: 2, patch: 0 },
|
||||
{ version: "2021.1.1", major: 2021, minor: 2 },
|
||||
|
||||
{ version: "2021.2.4", major: 0, minor: 113, patch: 0 },
|
||||
{ version: "2021.2.4", major: 0, minor: 113 },
|
||||
|
||||
{ version: "0.114.0", major: 0, minor: 113, patch: 0 },
|
||||
{ version: "0.114.0", major: 0, minor: 113 },
|
||||
|
||||
{ version: "2021.2.0dev.2323", major: 2021, minor: 2, patch: 0 },
|
||||
{ version: "2021.2.0dev.2323", major: 2021, minor: 2 },
|
||||
];
|
||||
|
||||
const testFalsyData = [
|
||||
{ version: "0.114.0", major: 0, minor: 113 },
|
||||
{ version: "2021.2.0dev.2323", major: 2021, minor: 2, patch: 0 },
|
||||
];
|
||||
|
||||
describe("atLeastVersion - Truthy", () => {
|
||||
testTruthyData.forEach((test) =>
|
||||
it(`'${test.version}' >= ${test.major},${test.minor},${test.patch}`, () => {
|
||||
assert.isTrue(atLeastVersion("2021.1.1", 2021, 1));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe("atLeastVersion - Falsy", () => {
|
||||
testFalsyData.forEach((test) =>
|
||||
it(`'${test.version}' >= ${test.major},${test.minor},${test.patch}`, () => {
|
||||
assert.isTrue(atLeastVersion("2021.1.1", 2021, 1));
|
||||
})
|
||||
);
|
||||
});
|
@ -754,7 +754,10 @@
|
||||
"time": {
|
||||
"after": "Na",
|
||||
"before": "Voor",
|
||||
"label": "Tyd"
|
||||
"label": "Tyd",
|
||||
"weekdays": {
|
||||
"mon": "Maandag"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "Entiteit met plek",
|
||||
|
@ -717,7 +717,10 @@
|
||||
"time": {
|
||||
"after": "بعد",
|
||||
"before": "قبل",
|
||||
"label": "وقت"
|
||||
"label": "وقت",
|
||||
"weekdays": {
|
||||
"mon": "الاثنين"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "كيان مع موقع",
|
||||
|
@ -186,6 +186,15 @@
|
||||
"no_blueprints": "আপনার কোনও ব্লুপ্রিন্ট নেই",
|
||||
"no_inputs": "এই ব্লুপ্রিন্টের কোনও ইনপুট নেই।"
|
||||
},
|
||||
"conditions": {
|
||||
"type": {
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "সোমবার"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"introduction": "ট্রিগারগুলি যা অটোমেশন নিয়মের প্রক্রিয়া শুরু করে। একই নিয়মের জন্য একাধিক ট্রিগার নির্দিষ্ট করা সম্ভব। ট্রিগার শুরু হয়ে গেলে, Home Assistant এর শর্তগুলি যথাযথভাবে প্রযোজ্য হবে, যদি থাকে তবে এবং অ্যাকশন কল করবে।",
|
||||
"type": {
|
||||
|
@ -242,6 +242,15 @@
|
||||
"config": {
|
||||
"automation": {
|
||||
"editor": {
|
||||
"conditions": {
|
||||
"type": {
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "Ponedeljak"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"type": {
|
||||
"mqtt": {
|
||||
|
@ -720,6 +720,12 @@
|
||||
"week": "fa {count} {count, plural,\n one {setmana}\n other {setmanes}\n}"
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"required": "Aquest camp és obligatori",
|
||||
"service_data": "Dades del servei",
|
||||
"target": "Objectiu",
|
||||
"target_description": "Objectiu d'aquesta crida de servei"
|
||||
},
|
||||
"service-picker": {
|
||||
"service": "Servei"
|
||||
},
|
||||
@ -1152,7 +1158,7 @@
|
||||
"event": {
|
||||
"event": "Esdeveniment:",
|
||||
"label": "Disparar esdeveniment",
|
||||
"service_data": "Dades de servei"
|
||||
"service_data": "Dades del servei"
|
||||
},
|
||||
"repeat": {
|
||||
"label": "Repeteix",
|
||||
@ -1988,8 +1994,10 @@
|
||||
"config_flow": {
|
||||
"aborted": "Avortat",
|
||||
"close": "Tanca",
|
||||
"could_not_load": "El flux de dades de configuració no s'ha pogut carregar",
|
||||
"created_config": "S'ha creat configuració per a {name}.",
|
||||
"dismiss": "Omet el diàleg",
|
||||
"error": "Error",
|
||||
"error_saving_area": "Error desant àrea: {error}",
|
||||
"external_step": {
|
||||
"description": "Aquest pas requereix que visitis un lloc web extern per completar-lo.",
|
||||
@ -1998,6 +2006,10 @@
|
||||
"finish": "Finalitza",
|
||||
"loading_first_time": "Espera mentre s'instal·la la integració",
|
||||
"not_all_required_fields": "No s'han omplert tots els camps obligatoris.",
|
||||
"pick_flow_step": {
|
||||
"new_flow": "No, configura una altra instància de {integration}",
|
||||
"title": "N'hem descobert aquests, vols configurar-los?"
|
||||
},
|
||||
"submit": "Envia"
|
||||
},
|
||||
"configure": "Configurar",
|
||||
@ -2857,7 +2869,9 @@
|
||||
"type": "Tipus d'esdeveniment"
|
||||
},
|
||||
"services": {
|
||||
"accepts_target": "Aquest servei accepta un objectiu, per exemple: `entity_id: light.bed_light`",
|
||||
"alert_parsing_yaml": "S'ha produït un error analitzant el codi YAML: {data}",
|
||||
"all_parameters": "Tots els paràmetres disponibles",
|
||||
"call_service": "Crida servei",
|
||||
"column_description": "Descripció",
|
||||
"column_example": "Exemple",
|
||||
@ -2868,7 +2882,10 @@
|
||||
"no_description": "No hi ha cap descripció disponible",
|
||||
"no_parameters": "Aquest servei no té paràmetres.",
|
||||
"select_service": "Selecciona un servei per veure'n la descripció",
|
||||
"title": "Serveis"
|
||||
"title": "Serveis",
|
||||
"ui_mode": "Vés al mode d'interfície d'usuari",
|
||||
"yaml_mode": "Vés al mode YAML",
|
||||
"yaml_parameters": "Paràmetres només disponibles en mode YAML"
|
||||
},
|
||||
"states": {
|
||||
"alert_entity_field": "L'entitat és un camp obligatori",
|
||||
|
@ -611,7 +611,10 @@
|
||||
"time": {
|
||||
"after": "Ar ôl",
|
||||
"before": "Cyn",
|
||||
"label": "Amser"
|
||||
"label": "Amser",
|
||||
"weekdays": {
|
||||
"mon": "Dydd Llun"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "Endid gyda lleoliad",
|
||||
|
@ -720,6 +720,12 @@
|
||||
"week": "{count} {count, plural,\n one {week}\n other {weeks}\n} ago"
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"required": "This field is required",
|
||||
"service_data": "Service data",
|
||||
"target": "Targets",
|
||||
"target_description": "What should this service use as targeted areas, devices or entities."
|
||||
},
|
||||
"service-picker": {
|
||||
"service": "Service"
|
||||
},
|
||||
@ -727,8 +733,8 @@
|
||||
"add_area_id": "Pick area",
|
||||
"add_device_id": "Pick device",
|
||||
"add_entity_id": "Pick entity",
|
||||
"expand_area_id": "Expand this area in the seperate devices and entities that it contains. After expanding it will not update the devices and entities when the area changes.",
|
||||
"expand_device_id": "Expand this device in seperate entities. After expanding it will not update the entities when the device changes.",
|
||||
"expand_area_id": "Expand this area into the separate devices and entities that it contains. After expanding, it will not update the devices and entities when the area changes.",
|
||||
"expand_device_id": "Expand this device into the separate entities that it contains. After expanding, it will not update the entities when the device changes.",
|
||||
"remove_area_id": "Remove area",
|
||||
"remove_device_id": "Remove device",
|
||||
"remove_entity_id": "Remove entity"
|
||||
@ -1988,8 +1994,10 @@
|
||||
"config_flow": {
|
||||
"aborted": "Aborted",
|
||||
"close": "Close",
|
||||
"could_not_load": "Config flow could not be loaded",
|
||||
"created_config": "Created configuration for {name}.",
|
||||
"dismiss": "Dismiss dialog",
|
||||
"error": "Error",
|
||||
"error_saving_area": "Error saving area: {error}",
|
||||
"external_step": {
|
||||
"description": "This step requires you to visit an external website to be completed.",
|
||||
@ -1998,6 +2006,10 @@
|
||||
"finish": "Finish",
|
||||
"loading_first_time": "Please wait while the integration is being installed",
|
||||
"not_all_required_fields": "Not all required fields are filled in.",
|
||||
"pick_flow_step": {
|
||||
"new_flow": "No, set up an other instance of {integration}",
|
||||
"title": "We discovered these, want to set them up?"
|
||||
},
|
||||
"submit": "Submit"
|
||||
},
|
||||
"configure": "Configure",
|
||||
@ -2857,7 +2869,9 @@
|
||||
"type": "Event Type"
|
||||
},
|
||||
"services": {
|
||||
"accepts_target": "This service accepts a target, for example: `entity_id: light.bed_light`",
|
||||
"alert_parsing_yaml": "Error parsing YAML: {data}",
|
||||
"all_parameters": "All available parameters",
|
||||
"call_service": "Call Service",
|
||||
"column_description": "Description",
|
||||
"column_example": "Example",
|
||||
@ -2868,7 +2882,10 @@
|
||||
"no_description": "No description is available",
|
||||
"no_parameters": "This service takes no parameters.",
|
||||
"select_service": "Select a service to see the description",
|
||||
"title": "Services"
|
||||
"title": "Services",
|
||||
"ui_mode": "Go to UI mode",
|
||||
"yaml_mode": "Go to YAML mode",
|
||||
"yaml_parameters": "Parameters only available in YAML mode"
|
||||
},
|
||||
"states": {
|
||||
"alert_entity_field": "Entity is a mandatory field",
|
||||
|
@ -7,6 +7,19 @@
|
||||
},
|
||||
"panel": {
|
||||
"config": {
|
||||
"automation": {
|
||||
"editor": {
|
||||
"conditions": {
|
||||
"type": {
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "Monday"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"integrations": {
|
||||
"caption": "Integrations",
|
||||
"configure": "Configure",
|
||||
|
@ -74,7 +74,14 @@
|
||||
}
|
||||
},
|
||||
"conditions": {
|
||||
"name": "Kondiĉo"
|
||||
"name": "Kondiĉo",
|
||||
"type": {
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "Lundo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit_ui": "Redakti kun UI",
|
||||
"edit_yaml": "Redakti kiel YAML",
|
||||
|
@ -720,6 +720,12 @@
|
||||
"week": "hace {count} {count, plural,\none {semana}\nother {semanas}\n}"
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"required": "Este campo es obligatorio",
|
||||
"service_data": "Datos de servicio",
|
||||
"target": "Objetivo",
|
||||
"target_description": "¿A qué debería dirigirse esta llamada de servicio?"
|
||||
},
|
||||
"service-picker": {
|
||||
"service": "Servicio"
|
||||
},
|
||||
@ -1988,8 +1994,10 @@
|
||||
"config_flow": {
|
||||
"aborted": "Abortado",
|
||||
"close": "Cerrar",
|
||||
"could_not_load": "No se pudo cargar el flujo de configuración",
|
||||
"created_config": "Configuración creada para {name}.",
|
||||
"dismiss": "Descartar diálogo",
|
||||
"error": "Error",
|
||||
"error_saving_area": "Error al guardar el área: {error}",
|
||||
"external_step": {
|
||||
"description": "Este paso requiere que visites una web externa para ser completado.",
|
||||
@ -1998,6 +2006,10 @@
|
||||
"finish": "Terminar",
|
||||
"loading_first_time": "Por favor, espera mientras la integración está siendo instalada",
|
||||
"not_all_required_fields": "No se han completado todos los campos requeridos.",
|
||||
"pick_flow_step": {
|
||||
"new_flow": "No, configura otra instancia de {integration}",
|
||||
"title": "Hemos descubierto éstas, ¿quieres configurarlas?"
|
||||
},
|
||||
"submit": "Enviar"
|
||||
},
|
||||
"configure": "Configurar",
|
||||
@ -2857,7 +2869,9 @@
|
||||
"type": "Tipo de evento"
|
||||
},
|
||||
"services": {
|
||||
"accepts_target": "Este servicio acepta un objetivo, por ejemplo: `entity_id: light.luz_dormitorio`",
|
||||
"alert_parsing_yaml": "Error al analizar YAML: {data}",
|
||||
"all_parameters": "Todos los parámetros disponibles",
|
||||
"call_service": "Llamar servicio",
|
||||
"column_description": "Descripción",
|
||||
"column_example": "Ejemplo",
|
||||
@ -2868,7 +2882,10 @@
|
||||
"no_description": "No hay descripción disponible.",
|
||||
"no_parameters": "Este servicio no toma parámetros.",
|
||||
"select_service": "Seleccione un servicio para ver la descripción.",
|
||||
"title": "Servicios"
|
||||
"title": "Servicios",
|
||||
"ui_mode": "Ir al modo IU",
|
||||
"yaml_mode": "Ir al modo YAML",
|
||||
"yaml_parameters": "Parámetros solo disponibles en modo YAML"
|
||||
},
|
||||
"states": {
|
||||
"alert_entity_field": "Entidad es un campo obligatorio",
|
||||
|
@ -720,6 +720,12 @@
|
||||
"week": "{count} {count, plural,\n one {nädala}\n other {nädala}\n} eest"
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"required": "See väli on nõutav",
|
||||
"service_data": "Teenuse andmed",
|
||||
"target": "Sihtmärk",
|
||||
"target_description": "Mida peaks see teenus välja kutsuma"
|
||||
},
|
||||
"service-picker": {
|
||||
"service": "Teenus"
|
||||
},
|
||||
@ -1988,8 +1994,10 @@
|
||||
"config_flow": {
|
||||
"aborted": "Katkestatud",
|
||||
"close": "Sulge",
|
||||
"could_not_load": "Konfiguratsioonivoogu ei saanud laadida",
|
||||
"created_config": "Üksuse {name} jaoks on loodud konfiguratsioon.",
|
||||
"dismiss": "Loobumisdialoog",
|
||||
"error": "Viga",
|
||||
"error_saving_area": "Viga ala salvestamisel: {error}",
|
||||
"external_step": {
|
||||
"description": "Selle etapi lõpetamine nõuab välise veebisaidi külastamist.",
|
||||
@ -1998,6 +2006,10 @@
|
||||
"finish": "Lõpeta",
|
||||
"loading_first_time": "Palun oodake kuni sidumist paigaldatakse",
|
||||
"not_all_required_fields": "Kõik nõutavad väljad pole täidetud.",
|
||||
"pick_flow_step": {
|
||||
"new_flow": "Ei, seadista teine {integration} eksemplar",
|
||||
"title": "Avastasime need, kas soovid neid seadistada?"
|
||||
},
|
||||
"submit": "Esita"
|
||||
},
|
||||
"configure": "Seadista",
|
||||
@ -2857,7 +2869,9 @@
|
||||
"type": "Sündmuse tüüp"
|
||||
},
|
||||
"services": {
|
||||
"accepts_target": "See teenus aktsepteerib sihtmärki, näiteks: \"entity_id: light.elutuba\"",
|
||||
"alert_parsing_yaml": "Viga YAML'i parsimisel: {data}",
|
||||
"all_parameters": "Kõik saadaolevad parameetrid",
|
||||
"call_service": "Kutsu teenus",
|
||||
"column_description": "Kirjeldus",
|
||||
"column_example": "Näide",
|
||||
@ -2868,7 +2882,10 @@
|
||||
"no_description": "Kirjeldus pole saadaval",
|
||||
"no_parameters": "Sellel teenusel pole parameetreid.",
|
||||
"select_service": "Kirjelduse kuvamiseks vali teenus",
|
||||
"title": "Teenused"
|
||||
"title": "Teenused",
|
||||
"ui_mode": "Ava kasutajaliidese režiim",
|
||||
"yaml_mode": "Redigeeri YAML-is",
|
||||
"yaml_parameters": "Parameetrid on saadaval ainult YAML režiimis"
|
||||
},
|
||||
"states": {
|
||||
"alert_entity_field": "Olem on kohustuslik väli",
|
||||
|
@ -442,7 +442,10 @@
|
||||
"value_template": "Valio txantiloia"
|
||||
},
|
||||
"time": {
|
||||
"label": "Denbora"
|
||||
"label": "Denbora",
|
||||
"weekdays": {
|
||||
"mon": "Astelehena"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -895,7 +895,10 @@
|
||||
"time": {
|
||||
"after": "بعد از",
|
||||
"before": "قبل از",
|
||||
"label": "زمان"
|
||||
"label": "زمان",
|
||||
"weekdays": {
|
||||
"mon": "دوشنبه"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "نهاد به همراخ موقعیت",
|
||||
|
@ -4,10 +4,12 @@
|
||||
"config": {
|
||||
"automation": {
|
||||
"editor": {
|
||||
"triggers": {
|
||||
"conditions": {
|
||||
"type": {
|
||||
"mqtt": {
|
||||
"label": ""
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "Luns"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +233,10 @@
|
||||
},
|
||||
"time": {
|
||||
"after": "बाद",
|
||||
"before": "पहले"
|
||||
"before": "पहले",
|
||||
"weekdays": {
|
||||
"mon": "सोमवार"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -731,7 +731,10 @@
|
||||
"time": {
|
||||
"after": "Nakon",
|
||||
"before": "Prije",
|
||||
"label": "Vrijeme"
|
||||
"label": "Vrijeme",
|
||||
"weekdays": {
|
||||
"mon": "ponedjeljak"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "Entitet sa lokacijom",
|
||||
|
@ -654,7 +654,10 @@
|
||||
"time": {
|
||||
"after": "Հետո",
|
||||
"before": "Նախքան",
|
||||
"label": "ժամանակը"
|
||||
"label": "ժամանակը",
|
||||
"weekdays": {
|
||||
"mon": "Երկուշաբթի"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "կազմակերպության գտնվելու վայրը",
|
||||
|
@ -5,6 +5,19 @@
|
||||
"ui": {
|
||||
"panel": {
|
||||
"config": {
|
||||
"automation": {
|
||||
"editor": {
|
||||
"conditions": {
|
||||
"type": {
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "ორშაბათს"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"blueprint": {
|
||||
"add": {
|
||||
"file_name": "ლოკალური გეგმა-ფაილის სახელი",
|
||||
|
@ -544,7 +544,10 @@
|
||||
"time": {
|
||||
"after": "Po",
|
||||
"before": "Prieš",
|
||||
"label": "Laikas"
|
||||
"label": "Laikas",
|
||||
"weekdays": {
|
||||
"mon": "Pirmadienis"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"label": "Vieta",
|
||||
|
@ -874,7 +874,10 @@
|
||||
"time": {
|
||||
"after": "Pēc",
|
||||
"before": "Pirms",
|
||||
"label": "Laiks"
|
||||
"label": "Laiks",
|
||||
"weekdays": {
|
||||
"mon": "Pirmdiena"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "Vienība ar atrašanos vietu",
|
||||
|
@ -3409,6 +3409,7 @@
|
||||
"playback_title": "Meldingsavspilling"
|
||||
},
|
||||
"my": {
|
||||
"component_not_loaded": "Denne viderekoblingen støttes ikke av Home Assistant-forekomsten. Du trenger integrasjonen {integration} å bruke denne viderekoblingen.",
|
||||
"error": "Det oppstod en ukjent feil",
|
||||
"faq_link": "Vanlige spørsmål om Min Home Assistant",
|
||||
"not_supported": "Denne viderekoblingen støttes ikke av Home Assistant-forekomsten. Se på {link} for viderekoblinger som støttes, og hvilken versjon de ble introdusert."
|
||||
|
@ -2002,7 +2002,7 @@
|
||||
},
|
||||
"configure": "Configureer",
|
||||
"configured": "Geconfigureerd",
|
||||
"confirm_new": "Wil je {integratie} instellen?",
|
||||
"confirm_new": "Wil je {integration} instellen?",
|
||||
"description": "Beheer integraties met services, apparaten, ...",
|
||||
"details": "Integratiedetails",
|
||||
"discovered": "Ontdekt",
|
||||
|
@ -766,7 +766,10 @@
|
||||
"time": {
|
||||
"after": "Etter",
|
||||
"before": "Før",
|
||||
"label": "Tid"
|
||||
"label": "Tid",
|
||||
"weekdays": {
|
||||
"mon": "mandag"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "Eining med posisjon",
|
||||
|
@ -720,6 +720,12 @@
|
||||
"week": "{count} {count, plural,\n one {tydzień}\n few {tygodnie}\n many {tygodni}\n other {tygodni}\n} temu"
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"required": "To pole jest wymagane",
|
||||
"service_data": "Dane usługi",
|
||||
"target": "Cel",
|
||||
"target_description": "Jaki powinien być cel wywołania tej usługi"
|
||||
},
|
||||
"service-picker": {
|
||||
"service": "Usługa"
|
||||
},
|
||||
@ -1988,8 +1994,10 @@
|
||||
"config_flow": {
|
||||
"aborted": "Przerwano",
|
||||
"close": "Zamknij",
|
||||
"could_not_load": "Nie można wczytać interfejsu konfiguracji",
|
||||
"created_config": "Utworzono konfigurację dla {name}.",
|
||||
"dismiss": "Okno dialogowe odrzucenia",
|
||||
"error": "Błąd",
|
||||
"error_saving_area": "Błąd podczas zapisywania obszaru: {error}",
|
||||
"external_step": {
|
||||
"description": "Ten krok wymaga od Ciebie odwiedzenia zewnętrznej strony.",
|
||||
@ -1998,6 +2006,10 @@
|
||||
"finish": "Zakończ",
|
||||
"loading_first_time": "Proszę czekać, trwa instalowanie integracji...",
|
||||
"not_all_required_fields": "Nie wszystkie wymagane pola są wypełnione.",
|
||||
"pick_flow_step": {
|
||||
"new_flow": "Nie, skonfiguruj inną instancję integracji {integration}",
|
||||
"title": "Odkryliśmy je, chcesz je skonfigurować?"
|
||||
},
|
||||
"submit": "Zatwierdź"
|
||||
},
|
||||
"configure": "Konfiguruj",
|
||||
@ -2857,7 +2869,9 @@
|
||||
"type": "Typ zdarzenia"
|
||||
},
|
||||
"services": {
|
||||
"accepts_target": "Ta usługa akceptuje cel, na przykład: `entity_id: light.bed_light`",
|
||||
"alert_parsing_yaml": "Błąd parsowania YAML: {data}",
|
||||
"all_parameters": "Wszystkie dostępne parametry",
|
||||
"call_service": "Wywołaj usługę",
|
||||
"column_description": "Opis",
|
||||
"column_example": "Przykład",
|
||||
@ -2868,7 +2882,10 @@
|
||||
"no_description": "Opis nie jest dostępny",
|
||||
"no_parameters": "Ta usługa nie przyjmuje parametrów.",
|
||||
"select_service": "Wybierz usługę, aby zobaczyć opis",
|
||||
"title": "Usługi"
|
||||
"title": "Usługi",
|
||||
"ui_mode": "Przejdź do trybu interfejsu użytkownika",
|
||||
"yaml_mode": "Przejdź do trybu YAML",
|
||||
"yaml_parameters": "Parametry dostępne tylko w trybie YAML"
|
||||
},
|
||||
"states": {
|
||||
"alert_entity_field": "Encja jest polem obowiązkowym",
|
||||
|
@ -720,6 +720,12 @@
|
||||
"week": "{count} {count, plural,\n one {нед.}\n other {нед.}\n} назад"
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"required": "Обязательное поле",
|
||||
"service_data": "Данные службы",
|
||||
"target": "Цель",
|
||||
"target_description": "Что эта служба должна использовать в качестве цели"
|
||||
},
|
||||
"service-picker": {
|
||||
"service": "Служба"
|
||||
},
|
||||
@ -1152,7 +1158,7 @@
|
||||
"event": {
|
||||
"event": "Событие:",
|
||||
"label": "Создание события",
|
||||
"service_data": "Данные"
|
||||
"service_data": "Данные службы"
|
||||
},
|
||||
"repeat": {
|
||||
"label": "Повтор",
|
||||
@ -1988,8 +1994,10 @@
|
||||
"config_flow": {
|
||||
"aborted": "Отменено",
|
||||
"close": "Закрыть",
|
||||
"could_not_load": "Не удалось загрузить мастер настройки",
|
||||
"created_config": "Создана конфигурация для {name}.",
|
||||
"dismiss": "Закрыть",
|
||||
"error": "Ошибка",
|
||||
"error_saving_area": "Ошибка сохранения помещения: {error}",
|
||||
"external_step": {
|
||||
"description": "Для завершения этого шага требуется посетить внешний веб-сайт.",
|
||||
@ -1998,6 +2006,10 @@
|
||||
"finish": "Готово",
|
||||
"loading_first_time": "Идет установка интеграции, пожалуйста, подождите",
|
||||
"not_all_required_fields": "Не все обязательные поля заполнены.",
|
||||
"pick_flow_step": {
|
||||
"new_flow": "Нет, настроить другой экземпляр {integration}",
|
||||
"title": "Это обнаружено автоматически, начать настройку?"
|
||||
},
|
||||
"submit": "Подтвердить"
|
||||
},
|
||||
"configure": "Настроить",
|
||||
@ -2857,7 +2869,9 @@
|
||||
"type": "Событие"
|
||||
},
|
||||
"services": {
|
||||
"accepts_target": "Эта служба принимает целевой объект, например: `entity_id: light.bed_light`",
|
||||
"alert_parsing_yaml": "Ошибка при разборе синтаксиса YAML: {data}",
|
||||
"all_parameters": "Все доступные параметры",
|
||||
"call_service": "Вызвать службу",
|
||||
"column_description": "Описание",
|
||||
"column_example": "Пример",
|
||||
@ -2868,7 +2882,10 @@
|
||||
"no_description": "Описание недоступно",
|
||||
"no_parameters": "Нет параметров для этой службы.",
|
||||
"select_service": "Выберите службу, чтобы увидеть описание.",
|
||||
"title": "Службы"
|
||||
"title": "Службы",
|
||||
"ui_mode": "Перейти в режим формы ввода",
|
||||
"yaml_mode": "Перейти в режим YAML",
|
||||
"yaml_parameters": "Параметры доступны только в режиме YAML"
|
||||
},
|
||||
"states": {
|
||||
"alert_entity_field": "Укажите объект.",
|
||||
|
@ -666,6 +666,9 @@
|
||||
"week": "Pred {count} {count, plural,\none {týždňom}\nother {týždňami}\n}"
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"service_data": "Dáta služby"
|
||||
},
|
||||
"service-picker": {
|
||||
"service": "Služba"
|
||||
},
|
||||
@ -1643,6 +1646,7 @@
|
||||
"close": "Zavrieť",
|
||||
"created_config": "Vytvorená konfigurácia pre {name}.",
|
||||
"dismiss": "Zrušiť dialógové okno",
|
||||
"error": "Chyba",
|
||||
"error_saving_area": "Chyba pri ukladaní oblasti: {error}",
|
||||
"external_step": {
|
||||
"description": "Tento krok vyžaduje, aby ste navštívili externú webovú stránku, ktorá dokončí proces.",
|
||||
|
@ -73,6 +73,15 @@
|
||||
"config": {
|
||||
"automation": {
|
||||
"editor": {
|
||||
"conditions": {
|
||||
"type": {
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "Понедељак"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"type": {
|
||||
"mqtt": {
|
||||
|
@ -56,6 +56,15 @@
|
||||
"config": {
|
||||
"automation": {
|
||||
"editor": {
|
||||
"conditions": {
|
||||
"type": {
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "Понедељак"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"learn_more": "Сазнајте више о окидачима",
|
||||
"type": {
|
||||
|
@ -239,6 +239,15 @@
|
||||
"config": {
|
||||
"automation": {
|
||||
"editor": {
|
||||
"conditions": {
|
||||
"type": {
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "திங்கட்கிழமை"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"type": {
|
||||
"mqtt": {
|
||||
|
@ -409,7 +409,10 @@
|
||||
},
|
||||
"time": {
|
||||
"after": "తరువాత",
|
||||
"before": "ముందు"
|
||||
"before": "ముందు",
|
||||
"weekdays": {
|
||||
"mon": "సోమవారం"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -670,7 +670,10 @@
|
||||
"time": {
|
||||
"after": "หลังจาก",
|
||||
"before": "ก่อนที่จะ",
|
||||
"label": "เวลา"
|
||||
"label": "เวลา",
|
||||
"weekdays": {
|
||||
"mon": "วันจันทร์"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"entity": "Entity พร้อมตำแหน่ง",
|
||||
|
@ -37,6 +37,11 @@
|
||||
"type": {
|
||||
"device": {
|
||||
"label": "آلہ"
|
||||
},
|
||||
"time": {
|
||||
"weekdays": {
|
||||
"mon": "پیر"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -720,6 +720,12 @@
|
||||
"week": "{count} {count, plural,\n one {週}\n other {週}\n}以前"
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"required": "必填欄位",
|
||||
"service_data": "服務資料",
|
||||
"target": "目標",
|
||||
"target_description": "此服務呼叫的目標"
|
||||
},
|
||||
"service-picker": {
|
||||
"service": "服務"
|
||||
},
|
||||
@ -1152,7 +1158,7 @@
|
||||
"event": {
|
||||
"event": "事件:",
|
||||
"label": "執行事件",
|
||||
"service_data": "資料"
|
||||
"service_data": "服務資料"
|
||||
},
|
||||
"repeat": {
|
||||
"label": "重複",
|
||||
@ -1988,8 +1994,10 @@
|
||||
"config_flow": {
|
||||
"aborted": "已中止",
|
||||
"close": "關閉",
|
||||
"could_not_load": "設定流程無法載入",
|
||||
"created_config": "新增 {name} 設定。",
|
||||
"dismiss": "關閉對話",
|
||||
"error": "錯誤",
|
||||
"error_saving_area": "儲存分區錯誤:{error}",
|
||||
"external_step": {
|
||||
"description": "此步驟將需要開啟外部網站方能完成。",
|
||||
@ -1998,6 +2006,10 @@
|
||||
"finish": "完成",
|
||||
"loading_first_time": "請稍候、正在安裝整合",
|
||||
"not_all_required_fields": "所有所需欄位都需要填寫。",
|
||||
"pick_flow_step": {
|
||||
"new_flow": "不,設定另一組 {integration} 實例",
|
||||
"title": "發現以下裝置,是否進行設定?"
|
||||
},
|
||||
"submit": "傳送"
|
||||
},
|
||||
"configure": "設定",
|
||||
@ -2857,7 +2869,9 @@
|
||||
"type": "事件類別"
|
||||
},
|
||||
"services": {
|
||||
"accepts_target": "服務接受目標,例如:`entity_id: light.bed_light`",
|
||||
"alert_parsing_yaml": "解析 YAML 錯誤:{data}",
|
||||
"all_parameters": "所有可用參數",
|
||||
"call_service": "執行服務",
|
||||
"column_description": "說明",
|
||||
"column_example": "範例",
|
||||
@ -2868,7 +2882,10 @@
|
||||
"no_description": "無描述可使用",
|
||||
"no_parameters": "此服務未含任何參數。",
|
||||
"select_service": "選擇服務以檢視其說明",
|
||||
"title": "服務"
|
||||
"title": "服務",
|
||||
"ui_mode": "進入 UI 模式",
|
||||
"yaml_mode": "進入 YAML 模式",
|
||||
"yaml_parameters": "參數僅限於 YAML 模式"
|
||||
},
|
||||
"states": {
|
||||
"alert_entity_field": "實體為必填欄位",
|
||||
|
Loading…
x
Reference in New Issue
Block a user