mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 08:16:36 +00:00
commit
c34dde815c
@ -1,6 +1,7 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@ -109,10 +110,18 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
margin: 16px 0;
|
||||
display: block;
|
||||
}
|
||||
.state {
|
||||
display: flex;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.state div {
|
||||
width: 180px;
|
||||
display: inline-block;
|
||||
}
|
||||
.state iron-icon {
|
||||
width: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
paper-toggle-button {
|
||||
display: inline;
|
||||
}
|
||||
@ -156,6 +165,9 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
margin-right: 4px;
|
||||
--iron-icon-height: 45px;
|
||||
}
|
||||
.protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template is="dom-if" if="[[computeUpdateAvailable(addon)]]">
|
||||
@ -188,6 +200,18 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
</paper-card>
|
||||
</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>
|
||||
<div class="card-content">
|
||||
<div class="addon-header">
|
||||
@ -226,22 +250,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
<img src="/api/hassio/addons/[[addonSlug]]/logo" />
|
||||
</a>
|
||||
</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">
|
||||
<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
|
||||
class$="[[computeSecurityClassName(addon.rating)]]"
|
||||
on-click="showMoreInfo"
|
||||
@ -348,22 +357,32 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
checked="[[addon.auto_update]]"
|
||||
></paper-toggle-button>
|
||||
</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>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
|
||||
on-change="protectionToggled"
|
||||
checked="[[addon.protected]]"
|
||||
></paper-toggle-button>
|
||||
</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>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
@ -580,5 +599,14 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
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);
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20190424.0",
|
||||
version="20190427.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -279,6 +279,14 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
const {
|
||||
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 { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
class PaperTimeInput extends PolymerElement {
|
||||
export class PaperTimeInput extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
|
@ -6,6 +6,12 @@ export interface FieldSchema {
|
||||
optional: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigFlowProgress {
|
||||
flow_id: string;
|
||||
handler: string;
|
||||
context: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface ConfigFlowStepForm {
|
||||
type: "form";
|
||||
flow_id: string;
|
||||
@ -62,3 +68,9 @@ export const handleConfigFlowStep = (
|
||||
|
||||
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||
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";
|
||||
|
||||
export const setOption = (
|
||||
export const setInputSelectOption = (
|
||||
hass: HomeAssistant,
|
||||
entity: 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 {
|
||||
fetchConfigFlow,
|
||||
createConfigFlow,
|
||||
ConfigFlowStep,
|
||||
deleteConfigFlow,
|
||||
getConfigFlowHandlers,
|
||||
} from "../../data/config_entries";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HaConfigFlowParams } from "./show-dialog-config-flow";
|
||||
|
||||
import "./step-flow-pick-handler";
|
||||
import "./step-flow-loading";
|
||||
import "./step-flow-form";
|
||||
import "./step-flow-abort";
|
||||
@ -39,6 +40,7 @@ import {
|
||||
fetchDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import { AreaRegistryEntry, fetchAreaRegistry } from "../../data/area_registry";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
let instance = 0;
|
||||
|
||||
@ -47,12 +49,15 @@ declare global {
|
||||
interface HASSDomEvents {
|
||||
"flow-update": {
|
||||
step?: ConfigFlowStep;
|
||||
stepPromise?: Promise<ConfigFlowStep>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("dialog-config-flow")
|
||||
class ConfigFlowDialog extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
private _params?: HaConfigFlowParams;
|
||||
|
||||
@ -62,7 +67,11 @@ class ConfigFlowDialog extends LitElement {
|
||||
private _instance = instance;
|
||||
|
||||
@property()
|
||||
private _step?: ConfigFlowStep;
|
||||
private _step:
|
||||
| ConfigFlowStep
|
||||
| undefined
|
||||
// Null means we need to pick a config flow
|
||||
| null;
|
||||
|
||||
@property()
|
||||
private _devices?: DeviceRegistryEntry[];
|
||||
@ -70,25 +79,35 @@ class ConfigFlowDialog extends LitElement {
|
||||
@property()
|
||||
private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@property()
|
||||
private _handlers?: string[];
|
||||
|
||||
public async showDialog(params: HaConfigFlowParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._loading = true;
|
||||
this._instance = instance++;
|
||||
|
||||
const fetchStep = params.continueFlowId
|
||||
? fetchConfigFlow(params.hass, params.continueFlowId)
|
||||
: params.newFlowForHandler
|
||||
? createConfigFlow(params.hass, params.newFlowForHandler)
|
||||
: undefined;
|
||||
// Create a new config flow. Show picker
|
||||
if (!params.continueFlowId) {
|
||||
this._step = null;
|
||||
|
||||
if (!fetchStep) {
|
||||
throw new Error(`Pass in either continueFlowId or newFlorForHandler`);
|
||||
// We only load the handlers once
|
||||
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;
|
||||
|
||||
await this.updateComplete;
|
||||
const step = await fetchStep;
|
||||
const step = await fetchConfigFlow(this.hass, params.continueFlowId);
|
||||
|
||||
// Happens if second showDialog called
|
||||
if (curInstance !== this._instance) {
|
||||
@ -99,7 +118,7 @@ class ConfigFlowDialog extends LitElement {
|
||||
this._loading = false;
|
||||
// When the flow changes, center the dialog.
|
||||
// Don't do it on each step or else the dialog keeps bouncing.
|
||||
setTimeout(() => this._dialog.center(), 0);
|
||||
this._scheduleCenterDialog();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
@ -113,7 +132,7 @@ class ConfigFlowDialog extends LitElement {
|
||||
opened
|
||||
@opened-changed=${this._openedChanged}
|
||||
>
|
||||
${this._loading
|
||||
${this._loading || (this._step === null && this._handlers === undefined)
|
||||
? html`
|
||||
<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
|
||||
// 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"
|
||||
? html`
|
||||
<step-flow-form
|
||||
.step=${this._step}
|
||||
.hass=${this._params.hass}
|
||||
.hass=${this.hass}
|
||||
></step-flow-form>
|
||||
`
|
||||
: this._step.type === "abort"
|
||||
? html`
|
||||
<step-flow-abort
|
||||
.step=${this._step}
|
||||
.hass=${this._params.hass}
|
||||
.hass=${this.hass}
|
||||
></step-flow-abort>
|
||||
`
|
||||
: this._devices === undefined || this._areas === undefined
|
||||
@ -143,7 +170,7 @@ class ConfigFlowDialog extends LitElement {
|
||||
: html`
|
||||
<step-flow-create-entry
|
||||
.step=${this._step}
|
||||
.hass=${this._params.hass}
|
||||
.hass=${this.hass}
|
||||
.devices=${this._devices}
|
||||
.areas=${this._areas}
|
||||
></step-flow-create-entry>
|
||||
@ -155,7 +182,8 @@ class ConfigFlowDialog extends LitElement {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
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 {
|
||||
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||
}
|
||||
@ -177,17 +209,29 @@ class ConfigFlowDialog extends LitElement {
|
||||
private async _fetchDevices(configEntryId) {
|
||||
// Wait 5 seconds to give integrations time to find devices
|
||||
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) =>
|
||||
device.config_entries.includes(configEntryId)
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
this._flowDone();
|
||||
return;
|
||||
@ -206,8 +250,8 @@ class ConfigFlowDialog extends LitElement {
|
||||
);
|
||||
|
||||
// If we created this flow, delete it now.
|
||||
if (this._step && !flowFinished && this._params.newFlowForHandler) {
|
||||
deleteConfigFlow(this._params.hass, this._step.flow_id);
|
||||
if (this._step && !flowFinished && !this._params.continueFlowId) {
|
||||
deleteConfigFlow(this.hass, this._step.flow_id);
|
||||
}
|
||||
|
||||
this._params.dialogClosedCallback({
|
||||
@ -221,8 +265,14 @@ class ConfigFlowDialog extends LitElement {
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
// Closed dialog by clicking on the overlay
|
||||
if (this._step && !ev.detail.value) {
|
||||
this._flowDone();
|
||||
if (!ev.detail.value) {
|
||||
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";
|
||||
|
||||
export interface HaConfigFlowParams {
|
||||
hass: HomeAssistant;
|
||||
continueFlowId?: string;
|
||||
newFlowForHandler?: string;
|
||||
dialogClosedCallback: (params: { flowFinished: boolean }) => void;
|
||||
}
|
||||
|
||||
|
@ -167,6 +167,8 @@ class StepFlowForm extends LitElement {
|
||||
toSendData
|
||||
);
|
||||
|
||||
// make sure we're still showing the same step as when we
|
||||
// fired off request.
|
||||
if (!this.step || flowId !== this.step.flow_id) {
|
||||
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
|
||||
? () =>
|
||||
import(/* webpackChunkName: "external_auth" */ "../external_app/external_auth").then(
|
||||
(mod) => new mod.default(hassUrl)
|
||||
({ createExternalAuth }) => createExternalAuth(hassUrl)
|
||||
)
|
||||
: () =>
|
||||
getAuth({
|
||||
|
@ -45,10 +45,10 @@ if (!window.externalApp && !window.webkit) {
|
||||
);
|
||||
}
|
||||
|
||||
export default class ExternalAuth extends Auth {
|
||||
public external = new ExternalMessaging();
|
||||
class ExternalAuth extends Auth {
|
||||
public external?: ExternalMessaging;
|
||||
|
||||
constructor(hassUrl) {
|
||||
constructor(hassUrl: string) {
|
||||
super({
|
||||
hassUrl,
|
||||
clientId: "",
|
||||
@ -58,7 +58,6 @@ export default class ExternalAuth extends Auth {
|
||||
// This will trigger connection to do a refresh right away
|
||||
expires: 0,
|
||||
});
|
||||
this.external.attach();
|
||||
}
|
||||
|
||||
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/paper-tooltip/paper-tooltip";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-fab/paper-fab";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
@ -13,6 +14,7 @@ import "../../../layouts/hass-subpage";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../components/ha-icon-next";
|
||||
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../ha-config-section";
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
@ -50,6 +52,28 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
||||
color: var(--primary-text-color);
|
||||
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>
|
||||
|
||||
<hass-subpage
|
||||
@ -119,23 +143,13 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
||||
</paper-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section>
|
||||
<span slot="header"
|
||||
>[[localize('ui.panel.config.integrations.new')]]</span
|
||||
>
|
||||
<paper-card>
|
||||
<template is="dom-repeat" items="[[handlers]]">
|
||||
<div class="config-entry-row">
|
||||
<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>
|
||||
<paper-fab
|
||||
icon="hass:plus"
|
||||
title="[[localize('ui.panel.config.integrations.new')]]"
|
||||
on-click="_createFlow"
|
||||
is-wide$="[[isWide]]"
|
||||
rtl$="[[rtl]]"
|
||||
></paper-fab>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
@ -162,6 +176,12 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
||||
progress: Array,
|
||||
|
||||
handlers: Array,
|
||||
|
||||
rtl: {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
computed: "_computeRTL(hass)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -170,17 +190,14 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
||||
loadConfigFlowDialog();
|
||||
}
|
||||
|
||||
_createFlow(ev) {
|
||||
_createFlow() {
|
||||
showConfigFlowDialog(this, {
|
||||
hass: this.hass,
|
||||
newFlowForHandler: ev.model.item,
|
||||
dialogClosedCallback: () => this.fire("hass-reload-entries"),
|
||||
});
|
||||
}
|
||||
|
||||
_continueFlow(ev) {
|
||||
showConfigFlowDialog(this, {
|
||||
hass: this.hass,
|
||||
continueFlowId: ev.model.item.flow_id,
|
||||
dialogClosedCallback: () => this.fire("hass-reload-entries"),
|
||||
});
|
||||
@ -230,6 +247,10 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
||||
_handleMoreInfo(ev) {
|
||||
this.fire("hass-more-info", { entityId: ev.model.item.entity_id });
|
||||
}
|
||||
|
||||
_computeRTL(hass) {
|
||||
return computeRTL(hass);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-config-entries-dashboard", HaConfigManagerDashboard);
|
||||
|
@ -11,6 +11,10 @@ interface Config extends LovelaceElementConfig {
|
||||
}
|
||||
|
||||
export const computeTooltip = (hass: HomeAssistant, config: Config): string => {
|
||||
if (config.title === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (config.title) {
|
||||
return config.title;
|
||||
}
|
||||
@ -26,10 +30,10 @@ export const computeTooltip = (hass: HomeAssistant, config: Config): string => {
|
||||
}
|
||||
|
||||
const tapTooltip = config.tap_action
|
||||
? computeActionTooltip(stateName, config.tap_action, false)
|
||||
? computeActionTooltip(hass, stateName, config.tap_action, false)
|
||||
: "";
|
||||
const holdTooltip = config.hold_action
|
||||
? computeActionTooltip(stateName, config.hold_action, true)
|
||||
? computeActionTooltip(hass, stateName, config.hold_action, true)
|
||||
: "";
|
||||
|
||||
const newline = tapTooltip && holdTooltip ? "\n" : "";
|
||||
@ -40,6 +44,7 @@ export const computeTooltip = (hass: HomeAssistant, config: Config): string => {
|
||||
};
|
||||
|
||||
function computeActionTooltip(
|
||||
hass: HomeAssistant,
|
||||
state: string,
|
||||
config: ActionConfig,
|
||||
isHold: boolean
|
||||
@ -48,20 +53,39 @@ function computeActionTooltip(
|
||||
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) {
|
||||
case "navigate":
|
||||
tooltip += `Navigate to ${config.navigation_path}`;
|
||||
tooltip += `${hass.localize(
|
||||
"ui.panel.lovelace.cards.picture-elements.navigate_to",
|
||||
"location",
|
||||
config.navigation_path
|
||||
)}`;
|
||||
break;
|
||||
case "toggle":
|
||||
tooltip += `Toggle ${state}`;
|
||||
tooltip += `${hass.localize(
|
||||
"ui.panel.lovelace.cards.picture-elements.toggle",
|
||||
"name",
|
||||
state
|
||||
)}`;
|
||||
break;
|
||||
case "call-service":
|
||||
tooltip += `Call service ${config.service}`;
|
||||
tooltip += `${hass.localize(
|
||||
"ui.panel.lovelace.cards.picture-elements.call_service",
|
||||
"name",
|
||||
config.service
|
||||
)}`;
|
||||
break;
|
||||
case "more-info":
|
||||
tooltip += `Show more-info: ${state}`;
|
||||
tooltip += `${hass.localize(
|
||||
"ui.panel.lovelace.cards.picture-elements.more_info",
|
||||
"name",
|
||||
state
|
||||
)}`;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import "../entity-rows/hui-climate-entity-row";
|
||||
import "../entity-rows/hui-cover-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-select-entity-row";
|
||||
import "../entity-rows/hui-input-text-entity-row";
|
||||
@ -58,6 +59,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
||||
// Temporary. Once climate is rewritten,
|
||||
// water heater should get it's own row.
|
||||
water_heater: "climate",
|
||||
input_datetime: "input-datetime",
|
||||
};
|
||||
const TIMEOUT = 2000;
|
||||
|
||||
|
@ -62,6 +62,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
|
||||
@ha-click="${this._handleClick}"
|
||||
@ha-hold="${this._handleHold}"
|
||||
.longPress="${longPress()}"
|
||||
.overrideIcon=${this._config.icon}
|
||||
></state-badge>
|
||||
`;
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ export interface StateIconElementConfig extends LovelaceElementConfig {
|
||||
entity: string;
|
||||
tap_action?: ActionConfig;
|
||||
hold_action?: ActionConfig;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
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,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { repeat } from "lit-html/directives/repeat";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
|
||||
@ -18,11 +17,12 @@ import "../components/hui-warning";
|
||||
|
||||
import computeStateName from "../../../common/entity/compute_state_name";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { HomeAssistant, InputSelectEntity } 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 { forwardHaptic } from "../../../util/haptics";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
|
||||
@customElement("hui-input-select-entity-row")
|
||||
class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
||||
@ -47,7 +47,9 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
const stateObj = this.hass.states[this._config.entity] as
|
||||
| InputSelectEntity
|
||||
| undefined;
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
@ -64,26 +66,43 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
||||
return html`
|
||||
<state-badge .stateObj="${stateObj}"></state-badge>
|
||||
<ha-paper-dropdown-menu
|
||||
selected-item-label="${stateObj.state}"
|
||||
@selected-item-label-changed="${this._selectedChanged}"
|
||||
label="${this._config.name || computeStateName(stateObj)}"
|
||||
.label=${this._config.name || computeStateName(stateObj)}
|
||||
.value=${stateObj.state}
|
||||
@iron-select=${this._selectedChanged}
|
||||
@click=${stopPropagation}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
selected="${stateObj.attributes.options.indexOf(stateObj.state)}"
|
||||
>
|
||||
${repeat(
|
||||
stateObj.attributes.options,
|
||||
(option) =>
|
||||
html`
|
||||
<paper-item>${option}</paper-item>
|
||||
`
|
||||
<paper-listbox slot="dropdown-content">
|
||||
${stateObj.attributes.options.map(
|
||||
(option) => html`
|
||||
<paper-item>${option}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</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 {
|
||||
return css`
|
||||
:host {
|
||||
@ -94,22 +113,28 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
||||
margin-left: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-width: 200px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
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];
|
||||
if (
|
||||
!ev.target.selectedItem ||
|
||||
ev.target.selectedItem.innerText === "" ||
|
||||
ev.target.selectedItem.innerText === stateObj.state
|
||||
) {
|
||||
const option = ev.detail.item.innerText;
|
||||
if (option === stateObj.state) {
|
||||
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 {
|
||||
// for fire event
|
||||
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",
|
||||
"clear_items": "Clear checked items",
|
||||
"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": {
|
||||
|
@ -219,6 +219,12 @@ export type CameraEntity = HassEntityBase & {
|
||||
};
|
||||
};
|
||||
|
||||
export type InputSelectEntity = HassEntityBase & {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
options: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export interface Route {
|
||||
prefix: string;
|
||||
path: string;
|
||||
|
@ -629,6 +629,9 @@
|
||||
"password": "Cyfrinair",
|
||||
"create": "Creu"
|
||||
}
|
||||
},
|
||||
"cloud": {
|
||||
"description_features": "Rheolaeth oddi cartref, integreiddio gyda Alexa a Google Assistant."
|
||||
}
|
||||
},
|
||||
"lovelace": {
|
||||
|
@ -178,7 +178,7 @@
|
||||
"stopped": "Σταμάτησε",
|
||||
"locked": "Κλειδωμένο",
|
||||
"unlocked": "Ξεκλείδωτο",
|
||||
"ok": "Ένταξη",
|
||||
"ok": "Εντάξει",
|
||||
"problem": "Πρόβλημα"
|
||||
},
|
||||
"input_boolean": {
|
||||
|
@ -581,7 +581,8 @@
|
||||
"cloud": {
|
||||
"caption": "Home Assistant Felhő",
|
||||
"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": {
|
||||
"caption": "Integrációk",
|
||||
|
@ -101,13 +101,24 @@
|
||||
"gas": "Gas",
|
||||
"manual": "Handvirkt"
|
||||
},
|
||||
"cover": {
|
||||
"open": "Opin",
|
||||
"opening": "Opna",
|
||||
"closed": "Lokað",
|
||||
"closing": "Loka"
|
||||
},
|
||||
"device_tracker": {
|
||||
"home": "Heima",
|
||||
"not_home": "Fjarverandi"
|
||||
},
|
||||
"group": {
|
||||
"off": "Óvirkur",
|
||||
"on": "Virkur",
|
||||
"home": "Heima",
|
||||
"not_home": "Fjarverandi",
|
||||
"open": "Opin",
|
||||
"opening": "Opna",
|
||||
"closed": "Lokuð",
|
||||
"closing": "Loka",
|
||||
"stopped": "Stöðvað",
|
||||
"locked": "Læst",
|
||||
@ -172,6 +183,7 @@
|
||||
"rainy": "Rigning",
|
||||
"snowy": "Snjókoma",
|
||||
"snowy-rainy": "Slydda",
|
||||
"sunny": "Sólskin",
|
||||
"windy": "Vindasamt",
|
||||
"windy-variant": "Vindasamt"
|
||||
},
|
||||
@ -229,12 +241,16 @@
|
||||
},
|
||||
"config": {
|
||||
"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": {
|
||||
"caption": "Almennt",
|
||||
"description": "Staðfesta að stillingarskráin þín sé rétt og stjórnun á miðlara",
|
||||
"section": {
|
||||
"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": {
|
||||
"heading": "Staðfesta stillingar",
|
||||
"check_config": "Athuga stillingar",
|
||||
"valid": "Stillingar í lagi!",
|
||||
"invalid": "Stillingar ógildar"
|
||||
@ -243,7 +259,8 @@
|
||||
"heading": "Endurhleðsla stillinga",
|
||||
"core": "Endurhlaða inn kjarna",
|
||||
"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": {
|
||||
"heading": "Stjórnun miðlara",
|
||||
@ -256,6 +273,7 @@
|
||||
},
|
||||
"customize": {
|
||||
"caption": "Séraðlögun",
|
||||
"description": "Séraðlögun fyrir einingarnar þínar",
|
||||
"picker": {
|
||||
"header": "Séraðlögun"
|
||||
}
|
||||
@ -265,6 +283,7 @@
|
||||
"description": "Stofna og breyta sjálfvirkni",
|
||||
"picker": {
|
||||
"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",
|
||||
"learn_more": "Læra meira um sjálfvirkni"
|
||||
},
|
||||
@ -300,6 +319,10 @@
|
||||
"mqtt": {
|
||||
"label": "MQTT"
|
||||
},
|
||||
"numeric_state": {
|
||||
"above": "Yfir",
|
||||
"below": "Undir"
|
||||
},
|
||||
"sun": {
|
||||
"label": "Sól",
|
||||
"event": "Viðburður:",
|
||||
@ -348,6 +371,10 @@
|
||||
"label": "Staða",
|
||||
"state": "Staða"
|
||||
},
|
||||
"numeric_state": {
|
||||
"above": "Yfir",
|
||||
"below": "Undir"
|
||||
},
|
||||
"sun": {
|
||||
"label": "Sól",
|
||||
"before": "Fyrir:",
|
||||
@ -656,6 +683,7 @@
|
||||
}
|
||||
},
|
||||
"page-onboarding": {
|
||||
"intro": "Ertu tilbúinn að vekja heimilið þitt, endurheimta friðhelgi þína og gerast þáttakandi í samfélagi grúskara á heimsvísu?",
|
||||
"user": {
|
||||
"intro": "Hefjumst handa með því að byrja á að stona notanda aðgang.",
|
||||
"required_field": "Skilyrt",
|
||||
@ -896,6 +924,7 @@
|
||||
"confirm": "Vista innskráningu"
|
||||
},
|
||||
"notification_drawer": {
|
||||
"click_to_configure": "Smelltu á hnappinn til að stilla {entity}",
|
||||
"empty": "Engar tilkynningar",
|
||||
"title": "Tilkynningar"
|
||||
}
|
||||
@ -904,11 +933,16 @@
|
||||
"automation": "Sjálfvirkni",
|
||||
"calendar": "Dagatal",
|
||||
"camera": "Myndavél",
|
||||
"climate": "Loftslag",
|
||||
"configurator": "Stillingarálfur",
|
||||
"conversation": "Samtal",
|
||||
"cover": "Gluggatjöld",
|
||||
"fan": "Vifta",
|
||||
"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",
|
||||
"lock": "Lás",
|
||||
"mailbox": "Pósthólf",
|
||||
@ -917,6 +951,7 @@
|
||||
"plant": "Planta",
|
||||
"proximity": "Nálægð",
|
||||
"scene": "Sena",
|
||||
"script": "Skrifta",
|
||||
"sensor": "Skynjari",
|
||||
"sun": "Sól",
|
||||
"switch": "Rofi",
|
||||
|
Loading…
x
Reference in New Issue
Block a user