mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
commit
c34dde815c
@ -1,6 +1,7 @@
|
|||||||
import "@polymer/iron-icon/iron-icon";
|
import "@polymer/iron-icon/iron-icon";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-card/paper-card";
|
import "@polymer/paper-card/paper-card";
|
||||||
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
@ -109,10 +110,18 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.state {
|
||||||
|
display: flex;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
.state div {
|
.state div {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
.state iron-icon {
|
||||||
|
width: 16px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
paper-toggle-button {
|
paper-toggle-button {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
@ -156,6 +165,9 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
--iron-icon-height: 45px;
|
--iron-icon-height: 45px;
|
||||||
}
|
}
|
||||||
|
.protection-enable mwc-button {
|
||||||
|
--mdc-theme-primary: white;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<template is="dom-if" if="[[computeUpdateAvailable(addon)]]">
|
<template is="dom-if" if="[[computeUpdateAvailable(addon)]]">
|
||||||
@ -188,6 +200,18 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
</paper-card>
|
</paper-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template is="dom-if" if="[[!addon.protected]]">
|
||||||
|
<paper-card heading="Warning: Protection mode is disabled!" class="warning">
|
||||||
|
<div class="card-content">
|
||||||
|
Protection mode on this add-on is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this add-on.
|
||||||
|
</div>
|
||||||
|
<div class="card-actions protection-enable">
|
||||||
|
<mwc-button on-click="protectionToggled">Enable Protection mode</mwc-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
<paper-card>
|
<paper-card>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="addon-header">
|
<div class="addon-header">
|
||||||
@ -226,22 +250,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
<img src="/api/hassio/addons/[[addonSlug]]/logo" />
|
<img src="/api/hassio/addons/[[addonSlug]]/logo" />
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template is="dom-if" if="[[!addon.protected]]">
|
|
||||||
<paper-card heading="Warning: Protection mode is disabled!" class="warning">
|
|
||||||
<div class="card-content">
|
|
||||||
Protection mode on this add-on is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this add-on.
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<mwc-button on-click="protectionToggled">Enable Protection mode</mwc-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</paper-card>
|
|
||||||
</template>
|
|
||||||
<div class="security">
|
<div class="security">
|
||||||
<h3>Add-on Security Rating</h3>
|
|
||||||
<div class="description light-color">
|
|
||||||
Hass.io provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.
|
|
||||||
</div>
|
|
||||||
<ha-label-badge
|
<ha-label-badge
|
||||||
class$="[[computeSecurityClassName(addon.rating)]]"
|
class$="[[computeSecurityClassName(addon.rating)]]"
|
||||||
on-click="showMoreInfo"
|
on-click="showMoreInfo"
|
||||||
@ -348,22 +357,32 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
checked="[[addon.auto_update]]"
|
checked="[[addon.auto_update]]"
|
||||||
></paper-toggle-button>
|
></paper-toggle-button>
|
||||||
</div>
|
</div>
|
||||||
|
<template is="dom-if" if="[[addon.ingress]]">
|
||||||
|
<div class="state">
|
||||||
|
<div>Show in sidebar</div>
|
||||||
|
<paper-toggle-button
|
||||||
|
on-change="panelToggled"
|
||||||
|
checked="[[addon.ingress_panel]]"
|
||||||
|
disabled="[[_computeCannotIngressSidebar(hass, addon)]]"
|
||||||
|
></paper-toggle-button>
|
||||||
|
<template is="dom-if" if="[[_computeCannotIngressSidebar(hass, addon)]]">
|
||||||
|
<span>This option requires Home Assistant 0.92 or later.</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div class="state">
|
<div class="state">
|
||||||
<div>Protection mode</div>
|
<div>
|
||||||
|
Protection mode
|
||||||
|
<span>
|
||||||
|
<iron-icon icon="hassio:information"></iron-icon>
|
||||||
|
<paper-tooltip>Grant the add-on elevated system access.</paper-tooltip>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<paper-toggle-button
|
<paper-toggle-button
|
||||||
on-change="protectionToggled"
|
on-change="protectionToggled"
|
||||||
checked="[[addon.protected]]"
|
checked="[[addon.protected]]"
|
||||||
></paper-toggle-button>
|
></paper-toggle-button>
|
||||||
</div>
|
</div>
|
||||||
<template is="dom-if" if="[[addon.ingress]]">
|
|
||||||
<div class="state">
|
|
||||||
<div>Show in Sidebar</div>
|
|
||||||
<paper-toggle-button
|
|
||||||
on-change="panelToggled"
|
|
||||||
checked="[[addon.ingress_panel]]"
|
|
||||||
></paper-toggle-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
@ -580,5 +599,14 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
this.fire("hass-api-called", eventData);
|
this.fire("hass-api-called", eventData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeCannotIngressSidebar(hass, addon) {
|
||||||
|
return !addon.ingress || !this._computeHA92plus(hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeHA92plus(hass) {
|
||||||
|
const [major, minor] = hass.config.version.split(".", 2);
|
||||||
|
return Number(major) > 0 || (major === "0" && Number(minor) >= 92);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
customElements.define("hassio-addon-info", HassioAddonInfo);
|
customElements.define("hassio-addon-info", HassioAddonInfo);
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20190424.0",
|
version="20190427.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -279,6 +279,14 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We have a new picture url
|
// We have a new picture url
|
||||||
|
// If entity picture is non-relative, we use that url directly.
|
||||||
|
if (picture.substr(0, 1) !== "/") {
|
||||||
|
this._coverShowing = true;
|
||||||
|
this._coverLoadError = false;
|
||||||
|
this.$.cover.style.backgroundImage = `url(${picture})`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
content_type: contentType,
|
content_type: contentType,
|
||||||
|
1
src/common/dom/stop_propagation.ts
Normal file
1
src/common/dom/stop_propagation.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const stopPropagation = (ev) => ev.stopPropagation();
|
126
src/components/ha-date-input.ts
Normal file
126
src/components/ha-date-input.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
// tslint:disable-next-line:no-duplicate-imports
|
||||||
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
|
@customElement("ha-date-input")
|
||||||
|
export class HaDateInput extends LitElement {
|
||||||
|
@property() public year?: string;
|
||||||
|
@property() public month?: string;
|
||||||
|
@property() public day?: string;
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
font-family: var(--paper-font-common-base_-_font-family);
|
||||||
|
-webkit-font-smoothing: var(
|
||||||
|
--paper-font-common-base_-_-webkit-font-smoothing
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-input {
|
||||||
|
width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
--paper-input-container-input_-_-moz-appearance: textfield;
|
||||||
|
--paper-input-container-input-webkit-spinner_-_-webkit-appearance: none;
|
||||||
|
--paper-input-container-input-webkit-spinner_-_margin: 0;
|
||||||
|
--paper-input-container-input-webkit-spinner_-_display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-input#year {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="date-input-wrap">
|
||||||
|
<paper-input
|
||||||
|
id="year"
|
||||||
|
type="number"
|
||||||
|
.value=${this.year}
|
||||||
|
@change=${this._formatYear}
|
||||||
|
maxlength="4"
|
||||||
|
max="9999"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
no-label-float
|
||||||
|
>
|
||||||
|
<span suffix="" slot="suffix">-</span>
|
||||||
|
</paper-input>
|
||||||
|
<paper-input
|
||||||
|
id="month"
|
||||||
|
type="number"
|
||||||
|
.value=${this.month}
|
||||||
|
@change=${this._formatMonth}
|
||||||
|
maxlength="2"
|
||||||
|
max="12"
|
||||||
|
min="1"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
no-label-float
|
||||||
|
>
|
||||||
|
<span suffix="" slot="suffix">-</span>
|
||||||
|
</paper-input>
|
||||||
|
<paper-input
|
||||||
|
id="day"
|
||||||
|
type="number"
|
||||||
|
.value=${this.day}
|
||||||
|
@change=${this._formatDay}
|
||||||
|
maxlength="2"
|
||||||
|
max="31"
|
||||||
|
min="1"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
no-label-float
|
||||||
|
>
|
||||||
|
</paper-input>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _formatYear() {
|
||||||
|
const yearElement = this.shadowRoot!.getElementById(
|
||||||
|
"year"
|
||||||
|
) as PaperInputElement;
|
||||||
|
this.year = yearElement.value!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _formatMonth() {
|
||||||
|
const monthElement = this.shadowRoot!.getElementById(
|
||||||
|
"month"
|
||||||
|
) as PaperInputElement;
|
||||||
|
this.month = ("0" + monthElement.value!).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _formatDay() {
|
||||||
|
const dayElement = this.shadowRoot!.getElementById(
|
||||||
|
"day"
|
||||||
|
) as PaperInputElement;
|
||||||
|
this.day = ("0" + dayElement.value!).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return `${this.year}-${this.month}-${this.day}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-date-input": HaDateInput;
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
class PaperTimeInput extends PolymerElement {
|
export class PaperTimeInput extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
|
@ -6,6 +6,12 @@ export interface FieldSchema {
|
|||||||
optional: boolean;
|
optional: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ConfigFlowProgress {
|
||||||
|
flow_id: string;
|
||||||
|
handler: string;
|
||||||
|
context: { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConfigFlowStepForm {
|
export interface ConfigFlowStepForm {
|
||||||
type: "form";
|
type: "form";
|
||||||
flow_id: string;
|
flow_id: string;
|
||||||
@ -62,3 +68,9 @@ export const handleConfigFlowStep = (
|
|||||||
|
|
||||||
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||||
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
|
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
|
||||||
|
|
||||||
|
export const getConfigFlowsInProgress = (hass: HomeAssistant) =>
|
||||||
|
hass.callApi<ConfigFlowProgress[]>("GET", "config/config_entries/flow");
|
||||||
|
|
||||||
|
export const getConfigFlowHandlers = (hass: HomeAssistant) =>
|
||||||
|
hass.callApi<string[]>("GET", "config/config_entries/flow_handlers");
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
export const setOption = (
|
export const setInputSelectOption = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity: string,
|
entity: string,
|
||||||
option: string
|
option: string
|
||||||
|
11
src/data/input_datetime.ts
Normal file
11
src/data/input_datetime.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export const setInputDateTimeValue = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityId: string,
|
||||||
|
time: string | undefined = undefined,
|
||||||
|
date: string | undefined = undefined
|
||||||
|
) => {
|
||||||
|
const param = { entity_id: entityId, time, date };
|
||||||
|
hass.callService(entityId.split(".", 1)[0], "set_datetime", param);
|
||||||
|
};
|
@ -23,13 +23,14 @@ import { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
|
|||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
import {
|
import {
|
||||||
fetchConfigFlow,
|
fetchConfigFlow,
|
||||||
createConfigFlow,
|
|
||||||
ConfigFlowStep,
|
ConfigFlowStep,
|
||||||
deleteConfigFlow,
|
deleteConfigFlow,
|
||||||
|
getConfigFlowHandlers,
|
||||||
} from "../../data/config_entries";
|
} from "../../data/config_entries";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HaConfigFlowParams } from "./show-dialog-config-flow";
|
import { HaConfigFlowParams } from "./show-dialog-config-flow";
|
||||||
|
|
||||||
|
import "./step-flow-pick-handler";
|
||||||
import "./step-flow-loading";
|
import "./step-flow-loading";
|
||||||
import "./step-flow-form";
|
import "./step-flow-form";
|
||||||
import "./step-flow-abort";
|
import "./step-flow-abort";
|
||||||
@ -39,6 +40,7 @@ import {
|
|||||||
fetchDeviceRegistry,
|
fetchDeviceRegistry,
|
||||||
} from "../../data/device_registry";
|
} from "../../data/device_registry";
|
||||||
import { AreaRegistryEntry, fetchAreaRegistry } from "../../data/area_registry";
|
import { AreaRegistryEntry, fetchAreaRegistry } from "../../data/area_registry";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
let instance = 0;
|
let instance = 0;
|
||||||
|
|
||||||
@ -47,12 +49,15 @@ declare global {
|
|||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"flow-update": {
|
"flow-update": {
|
||||||
step?: ConfigFlowStep;
|
step?: ConfigFlowStep;
|
||||||
|
stepPromise?: Promise<ConfigFlowStep>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("dialog-config-flow")
|
@customElement("dialog-config-flow")
|
||||||
class ConfigFlowDialog extends LitElement {
|
class ConfigFlowDialog extends LitElement {
|
||||||
|
public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
private _params?: HaConfigFlowParams;
|
private _params?: HaConfigFlowParams;
|
||||||
|
|
||||||
@ -62,7 +67,11 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
private _instance = instance;
|
private _instance = instance;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
private _step?: ConfigFlowStep;
|
private _step:
|
||||||
|
| ConfigFlowStep
|
||||||
|
| undefined
|
||||||
|
// Null means we need to pick a config flow
|
||||||
|
| null;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
private _devices?: DeviceRegistryEntry[];
|
private _devices?: DeviceRegistryEntry[];
|
||||||
@ -70,25 +79,35 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
@property()
|
@property()
|
||||||
private _areas?: AreaRegistryEntry[];
|
private _areas?: AreaRegistryEntry[];
|
||||||
|
|
||||||
|
@property()
|
||||||
|
private _handlers?: string[];
|
||||||
|
|
||||||
public async showDialog(params: HaConfigFlowParams): Promise<void> {
|
public async showDialog(params: HaConfigFlowParams): Promise<void> {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._loading = true;
|
|
||||||
this._instance = instance++;
|
this._instance = instance++;
|
||||||
|
|
||||||
const fetchStep = params.continueFlowId
|
// Create a new config flow. Show picker
|
||||||
? fetchConfigFlow(params.hass, params.continueFlowId)
|
if (!params.continueFlowId) {
|
||||||
: params.newFlowForHandler
|
this._step = null;
|
||||||
? createConfigFlow(params.hass, params.newFlowForHandler)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (!fetchStep) {
|
// We only load the handlers once
|
||||||
throw new Error(`Pass in either continueFlowId or newFlorForHandler`);
|
if (this._handlers === undefined) {
|
||||||
|
this._loading = true;
|
||||||
|
this.updateComplete.then(() => this._scheduleCenterDialog());
|
||||||
|
try {
|
||||||
|
this._handlers = await getConfigFlowHandlers(this.hass);
|
||||||
|
} finally {
|
||||||
|
this._loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.updateComplete;
|
||||||
|
this._scheduleCenterDialog();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._loading = true;
|
||||||
const curInstance = this._instance;
|
const curInstance = this._instance;
|
||||||
|
const step = await fetchConfigFlow(this.hass, params.continueFlowId);
|
||||||
await this.updateComplete;
|
|
||||||
const step = await fetchStep;
|
|
||||||
|
|
||||||
// Happens if second showDialog called
|
// Happens if second showDialog called
|
||||||
if (curInstance !== this._instance) {
|
if (curInstance !== this._instance) {
|
||||||
@ -99,7 +118,7 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
this._loading = false;
|
this._loading = false;
|
||||||
// When the flow changes, center the dialog.
|
// When the flow changes, center the dialog.
|
||||||
// Don't do it on each step or else the dialog keeps bouncing.
|
// Don't do it on each step or else the dialog keeps bouncing.
|
||||||
setTimeout(() => this._dialog.center(), 0);
|
this._scheduleCenterDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
@ -113,7 +132,7 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
opened
|
opened
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
>
|
>
|
||||||
${this._loading
|
${this._loading || (this._step === null && this._handlers === undefined)
|
||||||
? html`
|
? html`
|
||||||
<step-flow-loading></step-flow-loading>
|
<step-flow-loading></step-flow-loading>
|
||||||
`
|
`
|
||||||
@ -121,18 +140,26 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
? // When we are going to next step, we render 1 round of empty
|
? // When we are going to next step, we render 1 round of empty
|
||||||
// to reset the element.
|
// to reset the element.
|
||||||
""
|
""
|
||||||
|
: this._step === null
|
||||||
|
? // Show handler picker
|
||||||
|
html`
|
||||||
|
<step-flow-pick-handler
|
||||||
|
.hass=${this.hass}
|
||||||
|
.handlers=${this._handlers}
|
||||||
|
></step-flow-pick-handler>
|
||||||
|
`
|
||||||
: this._step.type === "form"
|
: this._step.type === "form"
|
||||||
? html`
|
? html`
|
||||||
<step-flow-form
|
<step-flow-form
|
||||||
.step=${this._step}
|
.step=${this._step}
|
||||||
.hass=${this._params.hass}
|
.hass=${this.hass}
|
||||||
></step-flow-form>
|
></step-flow-form>
|
||||||
`
|
`
|
||||||
: this._step.type === "abort"
|
: this._step.type === "abort"
|
||||||
? html`
|
? html`
|
||||||
<step-flow-abort
|
<step-flow-abort
|
||||||
.step=${this._step}
|
.step=${this._step}
|
||||||
.hass=${this._params.hass}
|
.hass=${this.hass}
|
||||||
></step-flow-abort>
|
></step-flow-abort>
|
||||||
`
|
`
|
||||||
: this._devices === undefined || this._areas === undefined
|
: this._devices === undefined || this._areas === undefined
|
||||||
@ -143,7 +170,7 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
: html`
|
: html`
|
||||||
<step-flow-create-entry
|
<step-flow-create-entry
|
||||||
.step=${this._step}
|
.step=${this._step}
|
||||||
.hass=${this._params.hass}
|
.hass=${this.hass}
|
||||||
.devices=${this._devices}
|
.devices=${this._devices}
|
||||||
.areas=${this._areas}
|
.areas=${this._areas}
|
||||||
></step-flow-create-entry>
|
></step-flow-create-entry>
|
||||||
@ -155,7 +182,8 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this.addEventListener("flow-update", (ev) => {
|
this.addEventListener("flow-update", (ev) => {
|
||||||
this._processStep((ev as any).detail.step);
|
const { step, stepPromise } = (ev as any).detail;
|
||||||
|
this._processStep(step || stepPromise);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +198,10 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _scheduleCenterDialog() {
|
||||||
|
setTimeout(() => this._dialog.center(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
private get _dialog(): HaPaperDialog {
|
private get _dialog(): HaPaperDialog {
|
||||||
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||||
}
|
}
|
||||||
@ -177,17 +209,29 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
private async _fetchDevices(configEntryId) {
|
private async _fetchDevices(configEntryId) {
|
||||||
// Wait 5 seconds to give integrations time to find devices
|
// Wait 5 seconds to give integrations time to find devices
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
const devices = await fetchDeviceRegistry(this._params!.hass);
|
const devices = await fetchDeviceRegistry(this.hass);
|
||||||
this._devices = devices.filter((device) =>
|
this._devices = devices.filter((device) =>
|
||||||
device.config_entries.includes(configEntryId)
|
device.config_entries.includes(configEntryId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchAreas() {
|
private async _fetchAreas() {
|
||||||
this._areas = await fetchAreaRegistry(this._params!.hass);
|
this._areas = await fetchAreaRegistry(this.hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _processStep(step: ConfigFlowStep): Promise<void> {
|
private async _processStep(
|
||||||
|
step: ConfigFlowStep | undefined | Promise<ConfigFlowStep>
|
||||||
|
): Promise<void> {
|
||||||
|
if (step instanceof Promise) {
|
||||||
|
this._loading = true;
|
||||||
|
try {
|
||||||
|
this._step = await step;
|
||||||
|
} finally {
|
||||||
|
this._loading = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (step === undefined) {
|
if (step === undefined) {
|
||||||
this._flowDone();
|
this._flowDone();
|
||||||
return;
|
return;
|
||||||
@ -206,8 +250,8 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// If we created this flow, delete it now.
|
// If we created this flow, delete it now.
|
||||||
if (this._step && !flowFinished && this._params.newFlowForHandler) {
|
if (this._step && !flowFinished && !this._params.continueFlowId) {
|
||||||
deleteConfigFlow(this._params.hass, this._step.flow_id);
|
deleteConfigFlow(this.hass, this._step.flow_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._params.dialogClosedCallback({
|
this._params.dialogClosedCallback({
|
||||||
@ -221,8 +265,14 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||||
// Closed dialog by clicking on the overlay
|
// Closed dialog by clicking on the overlay
|
||||||
if (this._step && !ev.detail.value) {
|
if (!ev.detail.value) {
|
||||||
this._flowDone();
|
if (this._step) {
|
||||||
|
this._flowDone();
|
||||||
|
} else if (this._step === null) {
|
||||||
|
// Flow aborted during picking flow
|
||||||
|
this._step = undefined;
|
||||||
|
this._params = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
export interface HaConfigFlowParams {
|
export interface HaConfigFlowParams {
|
||||||
hass: HomeAssistant;
|
|
||||||
continueFlowId?: string;
|
continueFlowId?: string;
|
||||||
newFlowForHandler?: string;
|
|
||||||
dialogClosedCallback: (params: { flowFinished: boolean }) => void;
|
dialogClosedCallback: (params: { flowFinished: boolean }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +167,8 @@ class StepFlowForm extends LitElement {
|
|||||||
toSendData
|
toSendData
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// make sure we're still showing the same step as when we
|
||||||
|
// fired off request.
|
||||||
if (!this.step || flowId !== this.step.flow_id) {
|
if (!this.step || flowId !== this.step.flow_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
67
src/dialogs/config-flow/step-flow-pick-handler.ts
Normal file
67
src/dialogs/config-flow/step-flow-pick-handler.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
customElement,
|
||||||
|
CSSResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { createConfigFlow } from "../../data/config_entries";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import "../../components/ha-icon-next";
|
||||||
|
|
||||||
|
@customElement("step-flow-pick-handler")
|
||||||
|
class StepFlowPickHandler extends LitElement {
|
||||||
|
public hass!: HomeAssistant;
|
||||||
|
public handlers!: string[];
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
|
||||||
|
<div>
|
||||||
|
${this.handlers.map(
|
||||||
|
(handler) =>
|
||||||
|
html`
|
||||||
|
<paper-item @click=${this._handlerPicked} .handler=${handler}>
|
||||||
|
<paper-item-body>
|
||||||
|
${this.hass.localize(`component.${handler}.config.title`)}
|
||||||
|
</paper-item-body>
|
||||||
|
<ha-icon-next></ha-icon-next>
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handlerPicked(ev) {
|
||||||
|
fireEvent(this, "flow-update", {
|
||||||
|
stepPromise: createConfigFlow(this.hass, ev.currentTarget.handler),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
h2 {
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 600px;
|
||||||
|
}
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"step-flow-pick-handler": StepFlowPickHandler;
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ const isExternal = location.search.includes("external_auth=1");
|
|||||||
const authProm = isExternal
|
const authProm = isExternal
|
||||||
? () =>
|
? () =>
|
||||||
import(/* webpackChunkName: "external_auth" */ "../external_app/external_auth").then(
|
import(/* webpackChunkName: "external_auth" */ "../external_app/external_auth").then(
|
||||||
(mod) => new mod.default(hassUrl)
|
({ createExternalAuth }) => createExternalAuth(hassUrl)
|
||||||
)
|
)
|
||||||
: () =>
|
: () =>
|
||||||
getAuth({
|
getAuth({
|
||||||
|
@ -45,10 +45,10 @@ if (!window.externalApp && !window.webkit) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ExternalAuth extends Auth {
|
class ExternalAuth extends Auth {
|
||||||
public external = new ExternalMessaging();
|
public external?: ExternalMessaging;
|
||||||
|
|
||||||
constructor(hassUrl) {
|
constructor(hassUrl: string) {
|
||||||
super({
|
super({
|
||||||
hassUrl,
|
hassUrl,
|
||||||
clientId: "",
|
clientId: "",
|
||||||
@ -58,7 +58,6 @@ export default class ExternalAuth extends Auth {
|
|||||||
// This will trigger connection to do a refresh right away
|
// This will trigger connection to do a refresh right away
|
||||||
expires: 0,
|
expires: 0,
|
||||||
});
|
});
|
||||||
this.external.attach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async refreshAccessToken() {
|
public async refreshAccessToken() {
|
||||||
@ -100,3 +99,15 @@ export default class ExternalAuth extends Auth {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createExternalAuth = (hassUrl: string) => {
|
||||||
|
const auth = new ExternalAuth(hassUrl);
|
||||||
|
if (
|
||||||
|
(window.externalApp && window.externalApp.externalBus) ||
|
||||||
|
(window.webkit && window.webkit.messageHandlers.externalBus)
|
||||||
|
) {
|
||||||
|
auth.external = new ExternalMessaging();
|
||||||
|
auth.external.attach();
|
||||||
|
}
|
||||||
|
return auth;
|
||||||
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-fab/paper-fab";
|
||||||
import "@polymer/paper-card/paper-card";
|
import "@polymer/paper-card/paper-card";
|
||||||
import "@polymer/iron-icon/iron-icon";
|
import "@polymer/iron-icon/iron-icon";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
@ -13,6 +14,7 @@ import "../../../layouts/hass-subpage";
|
|||||||
import "../../../resources/ha-style";
|
import "../../../resources/ha-style";
|
||||||
import "../../../components/ha-icon-next";
|
import "../../../components/ha-icon-next";
|
||||||
|
|
||||||
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import EventsMixin from "../../../mixins/events-mixin";
|
import EventsMixin from "../../../mixins/events-mixin";
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||||
@ -50,6 +52,28 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
paper-fab {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-fab[is-wide] {
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-fab[rtl] {
|
||||||
|
right: auto;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-fab[rtl][is-wide] {
|
||||||
|
bottom: 24px;
|
||||||
|
right: auto;
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<hass-subpage
|
<hass-subpage
|
||||||
@ -119,23 +143,13 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
|||||||
</paper-card>
|
</paper-card>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
|
|
||||||
<ha-config-section>
|
<paper-fab
|
||||||
<span slot="header"
|
icon="hass:plus"
|
||||||
>[[localize('ui.panel.config.integrations.new')]]</span
|
title="[[localize('ui.panel.config.integrations.new')]]"
|
||||||
>
|
on-click="_createFlow"
|
||||||
<paper-card>
|
is-wide$="[[isWide]]"
|
||||||
<template is="dom-repeat" items="[[handlers]]">
|
rtl$="[[rtl]]"
|
||||||
<div class="config-entry-row">
|
></paper-fab>
|
||||||
<paper-item-body>
|
|
||||||
[[_computeIntegrationTitle(localize, item)]]
|
|
||||||
</paper-item-body>
|
|
||||||
<mwc-button on-click="_createFlow"
|
|
||||||
>[[localize('ui.panel.config.integrations.configure')]]</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</paper-card>
|
|
||||||
</ha-config-section>
|
|
||||||
</hass-subpage>
|
</hass-subpage>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -162,6 +176,12 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
|||||||
progress: Array,
|
progress: Array,
|
||||||
|
|
||||||
handlers: Array,
|
handlers: Array,
|
||||||
|
|
||||||
|
rtl: {
|
||||||
|
type: Boolean,
|
||||||
|
reflectToAttribute: true,
|
||||||
|
computed: "_computeRTL(hass)",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,17 +190,14 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
|||||||
loadConfigFlowDialog();
|
loadConfigFlowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
_createFlow(ev) {
|
_createFlow() {
|
||||||
showConfigFlowDialog(this, {
|
showConfigFlowDialog(this, {
|
||||||
hass: this.hass,
|
|
||||||
newFlowForHandler: ev.model.item,
|
|
||||||
dialogClosedCallback: () => this.fire("hass-reload-entries"),
|
dialogClosedCallback: () => this.fire("hass-reload-entries"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_continueFlow(ev) {
|
_continueFlow(ev) {
|
||||||
showConfigFlowDialog(this, {
|
showConfigFlowDialog(this, {
|
||||||
hass: this.hass,
|
|
||||||
continueFlowId: ev.model.item.flow_id,
|
continueFlowId: ev.model.item.flow_id,
|
||||||
dialogClosedCallback: () => this.fire("hass-reload-entries"),
|
dialogClosedCallback: () => this.fire("hass-reload-entries"),
|
||||||
});
|
});
|
||||||
@ -230,6 +247,10 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
|||||||
_handleMoreInfo(ev) {
|
_handleMoreInfo(ev) {
|
||||||
this.fire("hass-more-info", { entityId: ev.model.item.entity_id });
|
this.fire("hass-more-info", { entityId: ev.model.item.entity_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeRTL(hass) {
|
||||||
|
return computeRTL(hass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("ha-config-entries-dashboard", HaConfigManagerDashboard);
|
customElements.define("ha-config-entries-dashboard", HaConfigManagerDashboard);
|
||||||
|
@ -11,6 +11,10 @@ interface Config extends LovelaceElementConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const computeTooltip = (hass: HomeAssistant, config: Config): string => {
|
export const computeTooltip = (hass: HomeAssistant, config: Config): string => {
|
||||||
|
if (config.title === null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
if (config.title) {
|
if (config.title) {
|
||||||
return config.title;
|
return config.title;
|
||||||
}
|
}
|
||||||
@ -26,10 +30,10 @@ export const computeTooltip = (hass: HomeAssistant, config: Config): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tapTooltip = config.tap_action
|
const tapTooltip = config.tap_action
|
||||||
? computeActionTooltip(stateName, config.tap_action, false)
|
? computeActionTooltip(hass, stateName, config.tap_action, false)
|
||||||
: "";
|
: "";
|
||||||
const holdTooltip = config.hold_action
|
const holdTooltip = config.hold_action
|
||||||
? computeActionTooltip(stateName, config.hold_action, true)
|
? computeActionTooltip(hass, stateName, config.hold_action, true)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const newline = tapTooltip && holdTooltip ? "\n" : "";
|
const newline = tapTooltip && holdTooltip ? "\n" : "";
|
||||||
@ -40,6 +44,7 @@ export const computeTooltip = (hass: HomeAssistant, config: Config): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function computeActionTooltip(
|
function computeActionTooltip(
|
||||||
|
hass: HomeAssistant,
|
||||||
state: string,
|
state: string,
|
||||||
config: ActionConfig,
|
config: ActionConfig,
|
||||||
isHold: boolean
|
isHold: boolean
|
||||||
@ -48,20 +53,39 @@ function computeActionTooltip(
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
let tooltip = isHold ? "Hold: " : "Tap: ";
|
let tooltip =
|
||||||
|
(isHold
|
||||||
|
? hass.localize("ui.panel.lovelace.cards.picture-elements.hold")
|
||||||
|
: hass.localize("ui.panel.lovelace.cards.picture-elements.tap")) + " ";
|
||||||
|
|
||||||
switch (config.action) {
|
switch (config.action) {
|
||||||
case "navigate":
|
case "navigate":
|
||||||
tooltip += `Navigate to ${config.navigation_path}`;
|
tooltip += `${hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.picture-elements.navigate_to",
|
||||||
|
"location",
|
||||||
|
config.navigation_path
|
||||||
|
)}`;
|
||||||
break;
|
break;
|
||||||
case "toggle":
|
case "toggle":
|
||||||
tooltip += `Toggle ${state}`;
|
tooltip += `${hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.picture-elements.toggle",
|
||||||
|
"name",
|
||||||
|
state
|
||||||
|
)}`;
|
||||||
break;
|
break;
|
||||||
case "call-service":
|
case "call-service":
|
||||||
tooltip += `Call service ${config.service}`;
|
tooltip += `${hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.picture-elements.call_service",
|
||||||
|
"name",
|
||||||
|
config.service
|
||||||
|
)}`;
|
||||||
break;
|
break;
|
||||||
case "more-info":
|
case "more-info":
|
||||||
tooltip += `Show more-info: ${state}`;
|
tooltip += `${hass.localize(
|
||||||
|
"ui.panel.lovelace.cards.picture-elements.more_info",
|
||||||
|
"name",
|
||||||
|
state
|
||||||
|
)}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
import "../entity-rows/hui-climate-entity-row";
|
import "../entity-rows/hui-climate-entity-row";
|
||||||
import "../entity-rows/hui-cover-entity-row";
|
import "../entity-rows/hui-cover-entity-row";
|
||||||
import "../entity-rows/hui-group-entity-row";
|
import "../entity-rows/hui-group-entity-row";
|
||||||
|
import "../entity-rows/hui-input-datetime-entity-row";
|
||||||
import "../entity-rows/hui-input-number-entity-row";
|
import "../entity-rows/hui-input-number-entity-row";
|
||||||
import "../entity-rows/hui-input-select-entity-row";
|
import "../entity-rows/hui-input-select-entity-row";
|
||||||
import "../entity-rows/hui-input-text-entity-row";
|
import "../entity-rows/hui-input-text-entity-row";
|
||||||
@ -58,6 +59,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
|||||||
// Temporary. Once climate is rewritten,
|
// Temporary. Once climate is rewritten,
|
||||||
// water heater should get it's own row.
|
// water heater should get it's own row.
|
||||||
water_heater: "climate",
|
water_heater: "climate",
|
||||||
|
input_datetime: "input-datetime",
|
||||||
};
|
};
|
||||||
const TIMEOUT = 2000;
|
const TIMEOUT = 2000;
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
|
|||||||
@ha-click="${this._handleClick}"
|
@ha-click="${this._handleClick}"
|
||||||
@ha-hold="${this._handleHold}"
|
@ha-hold="${this._handleHold}"
|
||||||
.longPress="${longPress()}"
|
.longPress="${longPress()}"
|
||||||
|
.overrideIcon=${this._config.icon}
|
||||||
></state-badge>
|
></state-badge>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ export interface StateIconElementConfig extends LovelaceElementConfig {
|
|||||||
entity: string;
|
entity: string;
|
||||||
tap_action?: ActionConfig;
|
tap_action?: ActionConfig;
|
||||||
hold_action?: ActionConfig;
|
hold_action?: ActionConfig;
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StateLabelElementConfig extends LovelaceElementConfig {
|
export interface StateLabelElementConfig extends LovelaceElementConfig {
|
||||||
|
128
src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts
Normal file
128
src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
|
import "../components/hui-generic-entity-row";
|
||||||
|
import "../../../components/paper-time-input.js";
|
||||||
|
// tslint:disable-next-line:no-duplicate-imports
|
||||||
|
import { PaperTimeInput } from "../../../components/paper-time-input.js";
|
||||||
|
import "../../../components/ha-date-input";
|
||||||
|
// tslint:disable-next-line:no-duplicate-imports
|
||||||
|
import { HaDateInput } from "../../../components/ha-date-input";
|
||||||
|
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { EntityRow, EntityConfig } from "./types";
|
||||||
|
import { setInputDateTimeValue } from "../../../data/input_datetime";
|
||||||
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
|
|
||||||
|
@customElement("hui-input-datetime-entity-row")
|
||||||
|
class HuiInputDatetimeEntityRow extends LitElement implements EntityRow {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
@property() private _config?: EntityConfig;
|
||||||
|
|
||||||
|
public setConfig(config: EntityConfig): void {
|
||||||
|
if (!config) {
|
||||||
|
throw new Error("Configuration error");
|
||||||
|
}
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
|
return hasConfigOrEntityChanged(this, changedProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateObj = this.hass.states[this._config.entity];
|
||||||
|
|
||||||
|
if (!stateObj) {
|
||||||
|
return html`
|
||||||
|
<hui-warning
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.warning.entity_not_found",
|
||||||
|
"entity",
|
||||||
|
this._config.entity
|
||||||
|
)}</hui-warning
|
||||||
|
>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hui-generic-entity-row .hass="${this.hass}" .config="${this._config}">
|
||||||
|
${stateObj.attributes.has_date
|
||||||
|
? html`
|
||||||
|
<ha-date-input
|
||||||
|
.year=${stateObj.attributes.year}
|
||||||
|
.month=${("0" + stateObj.attributes.month).slice(-2)}
|
||||||
|
.day=${("0" + stateObj.attributes.day).slice(-2)}
|
||||||
|
@change=${this._selectedValueChanged}
|
||||||
|
@click=${this._stopEventPropagation}
|
||||||
|
></ha-date-input>
|
||||||
|
${stateObj.attributes.has_time ? "," : ""}
|
||||||
|
`
|
||||||
|
: ``}
|
||||||
|
${stateObj.attributes.has_time
|
||||||
|
? html`
|
||||||
|
<paper-time-input
|
||||||
|
.hour=${stateObj.state === "unknown"
|
||||||
|
? ""
|
||||||
|
: ("0" + stateObj.attributes.hour).slice(-2)}
|
||||||
|
.min=${stateObj.state === "unknown"
|
||||||
|
? ""
|
||||||
|
: ("0" + stateObj.attributes.minute).slice(-2)}
|
||||||
|
.amPm=${false}
|
||||||
|
@change=${this._selectedValueChanged}
|
||||||
|
@click=${this._stopEventPropagation}
|
||||||
|
hide-label
|
||||||
|
format="24"
|
||||||
|
></paper-time-input>
|
||||||
|
`
|
||||||
|
: ``}
|
||||||
|
</hui-generic-entity-row>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _stopEventPropagation(ev: Event): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _timeInputEl(): PaperTimeInput {
|
||||||
|
return this.shadowRoot!.querySelector("paper-time-input")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _dateInputEl(): HaDateInput {
|
||||||
|
return this.shadowRoot!.querySelector("ha-date-input")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _selectedValueChanged(ev): void {
|
||||||
|
const stateObj = this.hass!.states[this._config!.entity];
|
||||||
|
|
||||||
|
const time =
|
||||||
|
this._timeInputEl !== null
|
||||||
|
? this._timeInputEl.value.trim() + ":00"
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const date =
|
||||||
|
this._dateInputEl !== null ? this._dateInputEl.value : undefined;
|
||||||
|
|
||||||
|
if (time !== stateObj.state) {
|
||||||
|
setInputDateTimeValue(this.hass!, stateObj.entity_id, time, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.target.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-input-datetime-entity-row": HuiInputDatetimeEntityRow;
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ import {
|
|||||||
customElement,
|
customElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { repeat } from "lit-html/directives/repeat";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
|
||||||
@ -18,11 +17,12 @@ import "../components/hui-warning";
|
|||||||
|
|
||||||
import computeStateName from "../../../common/entity/compute_state_name";
|
import computeStateName from "../../../common/entity/compute_state_name";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant, InputSelectEntity } from "../../../types";
|
||||||
import { EntityRow, EntityConfig } from "./types";
|
import { EntityRow, EntityConfig } from "./types";
|
||||||
import { setOption } from "../../../data/input-select";
|
import { setInputSelectOption } from "../../../data/input-select";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
import { forwardHaptic } from "../../../util/haptics";
|
import { forwardHaptic } from "../../../util/haptics";
|
||||||
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
|
|
||||||
@customElement("hui-input-select-entity-row")
|
@customElement("hui-input-select-entity-row")
|
||||||
class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
||||||
@ -47,7 +47,9 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity];
|
const stateObj = this.hass.states[this._config.entity] as
|
||||||
|
| InputSelectEntity
|
||||||
|
| undefined;
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return html`
|
return html`
|
||||||
@ -64,26 +66,43 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
|||||||
return html`
|
return html`
|
||||||
<state-badge .stateObj="${stateObj}"></state-badge>
|
<state-badge .stateObj="${stateObj}"></state-badge>
|
||||||
<ha-paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
selected-item-label="${stateObj.state}"
|
.label=${this._config.name || computeStateName(stateObj)}
|
||||||
@selected-item-label-changed="${this._selectedChanged}"
|
.value=${stateObj.state}
|
||||||
label="${this._config.name || computeStateName(stateObj)}"
|
@iron-select=${this._selectedChanged}
|
||||||
|
@click=${stopPropagation}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
<paper-listbox slot="dropdown-content">
|
||||||
slot="dropdown-content"
|
${stateObj.attributes.options.map(
|
||||||
selected="${stateObj.attributes.options.indexOf(stateObj.state)}"
|
(option) => html`
|
||||||
>
|
<paper-item>${option}</paper-item>
|
||||||
${repeat(
|
`
|
||||||
stateObj.attributes.options,
|
|
||||||
(option) =>
|
|
||||||
html`
|
|
||||||
<paper-item>${option}</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
)}
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</ha-paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
|
||||||
|
if (!this.hass || !this._config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateObj = this.hass.states[this._config.entity] as
|
||||||
|
| InputSelectEntity
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (!stateObj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update selected after rendering the items or else it won't work in Firefox
|
||||||
|
this.shadowRoot!.querySelector(
|
||||||
|
"paper-listbox"
|
||||||
|
)!.selected = stateObj.attributes.options.indexOf(stateObj.state);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
@ -94,22 +113,28 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
|||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _selectedChanged(ev): void {
|
private _selectedChanged(ev): void {
|
||||||
forwardHaptic(this, "light");
|
|
||||||
// Selected Option will transition to '' before transitioning to new value
|
|
||||||
const stateObj = this.hass!.states[this._config!.entity];
|
const stateObj = this.hass!.states[this._config!.entity];
|
||||||
if (
|
const option = ev.detail.item.innerText;
|
||||||
!ev.target.selectedItem ||
|
if (option === stateObj.state) {
|
||||||
ev.target.selectedItem.innerText === "" ||
|
|
||||||
ev.target.selectedItem.innerText === stateObj.state
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOption(this.hass!, stateObj.entity_id, ev.target.selectedItem.innerText);
|
forwardHaptic(this, "light");
|
||||||
|
|
||||||
|
setInputSelectOption(
|
||||||
|
this.hass!,
|
||||||
|
stateObj.entity_id,
|
||||||
|
ev.target.selectedItem.innerText
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,12 @@ export interface PolymerChangedEvent<T> extends Event {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PolymerIronSelectEvent<T> extends Event {
|
||||||
|
detail: {
|
||||||
|
item: T;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../components/entity/state-badge";
|
|
||||||
|
|
||||||
import computeStateName from "../common/entity/compute_state_name";
|
|
||||||
|
|
||||||
class StateCardInputSelect extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
state-badge {
|
|
||||||
float: left;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-dropdown-menu {
|
|
||||||
display: block;
|
|
||||||
margin-left: 53px;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
${this.stateBadgeTemplate}
|
|
||||||
<paper-dropdown-menu
|
|
||||||
on-click="stopPropagation"
|
|
||||||
selected-item-label="{{selectedOption}}"
|
|
||||||
label="[[_computeStateName(stateObj)]]"
|
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
selected="[[computeSelected(stateObj)]]"
|
|
||||||
>
|
|
||||||
<template is="dom-repeat" items="[[stateObj.attributes.options]]">
|
|
||||||
<paper-item>[[item]]</paper-item>
|
|
||||||
</template>
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get stateBadgeTemplate() {
|
|
||||||
return html`
|
|
||||||
<state-badge state-obj="[[stateObj]]"></state-badge>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
stateObj: Object,
|
|
||||||
inDialog: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
selectedOption: {
|
|
||||||
type: String,
|
|
||||||
observer: "selectedOptionChanged",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeStateName(stateObj) {
|
|
||||||
return computeStateName(stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeSelected(stateObj) {
|
|
||||||
return stateObj.attributes.options.indexOf(stateObj.state);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedOptionChanged(option) {
|
|
||||||
// Selected Option will transition to '' before transitioning to new value
|
|
||||||
if (option === "" || option === this.stateObj.state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.hass.callService("input_select", "select_option", {
|
|
||||||
option: option,
|
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stopPropagation(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define("state-card-input_select", StateCardInputSelect);
|
|
96
src/state-summary/state-card-input_select.ts
Normal file
96
src/state-summary/state-card-input_select.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
customElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
// tslint:disable-next-line: no-duplicate-imports
|
||||||
|
import { PaperItemElement } from "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
|
||||||
|
import "../components/entity/state-badge";
|
||||||
|
|
||||||
|
import computeStateName from "../common/entity/compute_state_name";
|
||||||
|
import { HomeAssistant, InputSelectEntity } from "../types";
|
||||||
|
import { setInputSelectOption } from "../data/input-select";
|
||||||
|
import { PolymerIronSelectEvent } from "../polymer-types";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
|
|
||||||
|
@customElement("state-card-input_select")
|
||||||
|
class StateCardInputSelect extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public stateObj!: InputSelectEntity;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<state-badge .stateObj=${this.stateObj}></state-badge>
|
||||||
|
<paper-dropdown-menu-light
|
||||||
|
.label=${computeStateName(this.stateObj)}
|
||||||
|
.value="${this.stateObj.state}"
|
||||||
|
@iron-select=${this._selectedOptionChanged}
|
||||||
|
@click=${stopPropagation}
|
||||||
|
>
|
||||||
|
<paper-listbox slot="dropdown-content">
|
||||||
|
${this.stateObj.attributes.options.map(
|
||||||
|
(option) => html`
|
||||||
|
<paper-item>${option}</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu-light>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
// Update selected after rendering the items or else it won't work in Firefox
|
||||||
|
this.shadowRoot!.querySelector(
|
||||||
|
"paper-listbox"
|
||||||
|
)!.selected = this.stateObj.attributes.options.indexOf(this.stateObj.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _selectedOptionChanged(
|
||||||
|
ev: PolymerIronSelectEvent<PaperItemElement>
|
||||||
|
) {
|
||||||
|
const option = ev.detail.item.innerText;
|
||||||
|
if (option === this.stateObj.state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setInputSelectOption(this.hass, this.stateObj.entity_id, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
state-badge {
|
||||||
|
float: left;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-dropdown-menu-light {
|
||||||
|
display: block;
|
||||||
|
margin-left: 53px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"state-card-input_select": StateCardInputSelect;
|
||||||
|
}
|
||||||
|
}
|
@ -916,6 +916,14 @@
|
|||||||
"checked_items": "Checked items",
|
"checked_items": "Checked items",
|
||||||
"clear_items": "Clear checked items",
|
"clear_items": "Clear checked items",
|
||||||
"add_item": "Add item"
|
"add_item": "Add item"
|
||||||
|
},
|
||||||
|
"picture-elements": {
|
||||||
|
"hold": "Hold:",
|
||||||
|
"tap": "Tap:",
|
||||||
|
"navigate_to": "Navigate to {location}",
|
||||||
|
"toggle": "Toggle {name}",
|
||||||
|
"call_service": "Call service {name}",
|
||||||
|
"more_info": "Show more-info: {name}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
|
@ -219,6 +219,12 @@ export type CameraEntity = HassEntityBase & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InputSelectEntity = HassEntityBase & {
|
||||||
|
attributes: HassEntityAttributeBase & {
|
||||||
|
options: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export interface Route {
|
export interface Route {
|
||||||
prefix: string;
|
prefix: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -629,6 +629,9 @@
|
|||||||
"password": "Cyfrinair",
|
"password": "Cyfrinair",
|
||||||
"create": "Creu"
|
"create": "Creu"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cloud": {
|
||||||
|
"description_features": "Rheolaeth oddi cartref, integreiddio gyda Alexa a Google Assistant."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lovelace": {
|
"lovelace": {
|
||||||
|
@ -178,7 +178,7 @@
|
|||||||
"stopped": "Σταμάτησε",
|
"stopped": "Σταμάτησε",
|
||||||
"locked": "Κλειδωμένο",
|
"locked": "Κλειδωμένο",
|
||||||
"unlocked": "Ξεκλείδωτο",
|
"unlocked": "Ξεκλείδωτο",
|
||||||
"ok": "Ένταξη",
|
"ok": "Εντάξει",
|
||||||
"problem": "Πρόβλημα"
|
"problem": "Πρόβλημα"
|
||||||
},
|
},
|
||||||
"input_boolean": {
|
"input_boolean": {
|
||||||
|
@ -581,7 +581,8 @@
|
|||||||
"cloud": {
|
"cloud": {
|
||||||
"caption": "Home Assistant Felhő",
|
"caption": "Home Assistant Felhő",
|
||||||
"description_login": "Bejelentkezve mint {email}",
|
"description_login": "Bejelentkezve mint {email}",
|
||||||
"description_not_login": "Nincs bejelentkezve"
|
"description_not_login": "Nincs bejelentkezve",
|
||||||
|
"description_features": "Távoli vezérlés, Alexa és Google Assistant integráció"
|
||||||
},
|
},
|
||||||
"integrations": {
|
"integrations": {
|
||||||
"caption": "Integrációk",
|
"caption": "Integrációk",
|
||||||
|
@ -101,13 +101,24 @@
|
|||||||
"gas": "Gas",
|
"gas": "Gas",
|
||||||
"manual": "Handvirkt"
|
"manual": "Handvirkt"
|
||||||
},
|
},
|
||||||
|
"cover": {
|
||||||
|
"open": "Opin",
|
||||||
|
"opening": "Opna",
|
||||||
|
"closed": "Lokað",
|
||||||
|
"closing": "Loka"
|
||||||
|
},
|
||||||
"device_tracker": {
|
"device_tracker": {
|
||||||
"home": "Heima",
|
"home": "Heima",
|
||||||
"not_home": "Fjarverandi"
|
"not_home": "Fjarverandi"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
|
"off": "Óvirkur",
|
||||||
|
"on": "Virkur",
|
||||||
"home": "Heima",
|
"home": "Heima",
|
||||||
"not_home": "Fjarverandi",
|
"not_home": "Fjarverandi",
|
||||||
|
"open": "Opin",
|
||||||
|
"opening": "Opna",
|
||||||
|
"closed": "Lokuð",
|
||||||
"closing": "Loka",
|
"closing": "Loka",
|
||||||
"stopped": "Stöðvað",
|
"stopped": "Stöðvað",
|
||||||
"locked": "Læst",
|
"locked": "Læst",
|
||||||
@ -172,6 +183,7 @@
|
|||||||
"rainy": "Rigning",
|
"rainy": "Rigning",
|
||||||
"snowy": "Snjókoma",
|
"snowy": "Snjókoma",
|
||||||
"snowy-rainy": "Slydda",
|
"snowy-rainy": "Slydda",
|
||||||
|
"sunny": "Sólskin",
|
||||||
"windy": "Vindasamt",
|
"windy": "Vindasamt",
|
||||||
"windy-variant": "Vindasamt"
|
"windy-variant": "Vindasamt"
|
||||||
},
|
},
|
||||||
@ -229,12 +241,16 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"header": "Stilla af Home Assistant",
|
"header": "Stilla af Home Assistant",
|
||||||
|
"introduction": "Hér er mögulegt að stilla af íhluti og Home Assistang. Því miður er ekki hægt að breyta öllu í gegnum viðmótið ennþá, en við erum að vinna í því.",
|
||||||
"core": {
|
"core": {
|
||||||
"caption": "Almennt",
|
"caption": "Almennt",
|
||||||
"description": "Staðfesta að stillingarskráin þín sé rétt og stjórnun á miðlara",
|
"description": "Staðfesta að stillingarskráin þín sé rétt og stjórnun á miðlara",
|
||||||
"section": {
|
"section": {
|
||||||
"core": {
|
"core": {
|
||||||
|
"header": "Stillingar og stjórnun þjóns",
|
||||||
|
"introduction": "Að breyta stillingum getur verið þreytandi ferli og við vitum það. Þetta svæði mun reyna að létta þér lífið hvað það varðar.",
|
||||||
"validation": {
|
"validation": {
|
||||||
|
"heading": "Staðfesta stillingar",
|
||||||
"check_config": "Athuga stillingar",
|
"check_config": "Athuga stillingar",
|
||||||
"valid": "Stillingar í lagi!",
|
"valid": "Stillingar í lagi!",
|
||||||
"invalid": "Stillingar ógildar"
|
"invalid": "Stillingar ógildar"
|
||||||
@ -243,7 +259,8 @@
|
|||||||
"heading": "Endurhleðsla stillinga",
|
"heading": "Endurhleðsla stillinga",
|
||||||
"core": "Endurhlaða inn kjarna",
|
"core": "Endurhlaða inn kjarna",
|
||||||
"group": "Endurhlaða inn hópum",
|
"group": "Endurhlaða inn hópum",
|
||||||
"automation": "Endurhlaða inn sjálfvirkni"
|
"automation": "Endurhlaða inn sjálfvirkni",
|
||||||
|
"script": "Endurhlaða inn skriftum"
|
||||||
},
|
},
|
||||||
"server_management": {
|
"server_management": {
|
||||||
"heading": "Stjórnun miðlara",
|
"heading": "Stjórnun miðlara",
|
||||||
@ -256,6 +273,7 @@
|
|||||||
},
|
},
|
||||||
"customize": {
|
"customize": {
|
||||||
"caption": "Séraðlögun",
|
"caption": "Séraðlögun",
|
||||||
|
"description": "Séraðlögun fyrir einingarnar þínar",
|
||||||
"picker": {
|
"picker": {
|
||||||
"header": "Séraðlögun"
|
"header": "Séraðlögun"
|
||||||
}
|
}
|
||||||
@ -265,6 +283,7 @@
|
|||||||
"description": "Stofna og breyta sjálfvirkni",
|
"description": "Stofna og breyta sjálfvirkni",
|
||||||
"picker": {
|
"picker": {
|
||||||
"pick_automation": "Veldu sjálfvirkni sem á að breyta",
|
"pick_automation": "Veldu sjálfvirkni sem á að breyta",
|
||||||
|
"no_automations": "Við fundum ekki neinar sjálfvirkni-skilgreiningar sem hægt er að breyta",
|
||||||
"add_automation": "Bæta við sjálfvirkni",
|
"add_automation": "Bæta við sjálfvirkni",
|
||||||
"learn_more": "Læra meira um sjálfvirkni"
|
"learn_more": "Læra meira um sjálfvirkni"
|
||||||
},
|
},
|
||||||
@ -300,6 +319,10 @@
|
|||||||
"mqtt": {
|
"mqtt": {
|
||||||
"label": "MQTT"
|
"label": "MQTT"
|
||||||
},
|
},
|
||||||
|
"numeric_state": {
|
||||||
|
"above": "Yfir",
|
||||||
|
"below": "Undir"
|
||||||
|
},
|
||||||
"sun": {
|
"sun": {
|
||||||
"label": "Sól",
|
"label": "Sól",
|
||||||
"event": "Viðburður:",
|
"event": "Viðburður:",
|
||||||
@ -348,6 +371,10 @@
|
|||||||
"label": "Staða",
|
"label": "Staða",
|
||||||
"state": "Staða"
|
"state": "Staða"
|
||||||
},
|
},
|
||||||
|
"numeric_state": {
|
||||||
|
"above": "Yfir",
|
||||||
|
"below": "Undir"
|
||||||
|
},
|
||||||
"sun": {
|
"sun": {
|
||||||
"label": "Sól",
|
"label": "Sól",
|
||||||
"before": "Fyrir:",
|
"before": "Fyrir:",
|
||||||
@ -656,6 +683,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"page-onboarding": {
|
"page-onboarding": {
|
||||||
|
"intro": "Ertu tilbúinn að vekja heimilið þitt, endurheimta friðhelgi þína og gerast þáttakandi í samfélagi grúskara á heimsvísu?",
|
||||||
"user": {
|
"user": {
|
||||||
"intro": "Hefjumst handa með því að byrja á að stona notanda aðgang.",
|
"intro": "Hefjumst handa með því að byrja á að stona notanda aðgang.",
|
||||||
"required_field": "Skilyrt",
|
"required_field": "Skilyrt",
|
||||||
@ -896,6 +924,7 @@
|
|||||||
"confirm": "Vista innskráningu"
|
"confirm": "Vista innskráningu"
|
||||||
},
|
},
|
||||||
"notification_drawer": {
|
"notification_drawer": {
|
||||||
|
"click_to_configure": "Smelltu á hnappinn til að stilla {entity}",
|
||||||
"empty": "Engar tilkynningar",
|
"empty": "Engar tilkynningar",
|
||||||
"title": "Tilkynningar"
|
"title": "Tilkynningar"
|
||||||
}
|
}
|
||||||
@ -904,11 +933,16 @@
|
|||||||
"automation": "Sjálfvirkni",
|
"automation": "Sjálfvirkni",
|
||||||
"calendar": "Dagatal",
|
"calendar": "Dagatal",
|
||||||
"camera": "Myndavél",
|
"camera": "Myndavél",
|
||||||
|
"climate": "Loftslag",
|
||||||
"configurator": "Stillingarálfur",
|
"configurator": "Stillingarálfur",
|
||||||
"conversation": "Samtal",
|
"conversation": "Samtal",
|
||||||
"cover": "Gluggatjöld",
|
"cover": "Gluggatjöld",
|
||||||
"fan": "Vifta",
|
"fan": "Vifta",
|
||||||
"group": "Hópur",
|
"group": "Hópur",
|
||||||
|
"input_datetime": "Innsláttar dagsetning\/tími",
|
||||||
|
"input_select": "Innsláttarval",
|
||||||
|
"input_number": "Innsláttarnúmer",
|
||||||
|
"input_text": "Innsláttartexti",
|
||||||
"light": "Ljós",
|
"light": "Ljós",
|
||||||
"lock": "Lás",
|
"lock": "Lás",
|
||||||
"mailbox": "Pósthólf",
|
"mailbox": "Pósthólf",
|
||||||
@ -917,6 +951,7 @@
|
|||||||
"plant": "Planta",
|
"plant": "Planta",
|
||||||
"proximity": "Nálægð",
|
"proximity": "Nálægð",
|
||||||
"scene": "Sena",
|
"scene": "Sena",
|
||||||
|
"script": "Skrifta",
|
||||||
"sensor": "Skynjari",
|
"sensor": "Skynjari",
|
||||||
"sun": "Sól",
|
"sun": "Sól",
|
||||||
"switch": "Rofi",
|
"switch": "Rofi",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user