Compare commits

..

4 Commits

Author SHA1 Message Date
Bram Kragten
61dbae8b8b Merge pull request #6792 from home-assistant/dev 2020-09-04 23:26:27 +02:00
Bram Kragten
ba3cc7df0f Merge pull request #6765 from home-assistant/dev 2020-09-01 23:44:41 +02:00
Bram Kragten
090ad34f78 Merge pull request #6692 from home-assistant/dev 2020-08-24 20:48:44 +02:00
Bram Kragten
b2460cbc3d Merge pull request #6662 from home-assistant/dev 2020-08-20 16:10:18 +02:00
175 changed files with 2756 additions and 5472 deletions

View File

@@ -1,60 +0,0 @@
name: "CodeQL"
on:
push:
branches: [dev, master]
pull_request:
# The branches below must be a subset of the branches above
branches: [dev]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card";
import "../../../src/dialogs/more-info/more-info-content";
import "../../../src/state-summary/state-card-content";
import "./more-info-content";
class DemoMoreInfo extends PolymerElement {
static get template() {

View File

@@ -1,73 +0,0 @@
import { HassEntity } from "home-assistant-js-websocket";
import { property, PropertyValues, UpdatingElement } from "lit-element";
import dynamicContentUpdater from "../../../src/common/dom/dynamic_content_updater";
import { stateMoreInfoType } from "../../../src/common/entity/state_more_info_type";
import "../../../src/dialogs/more-info/controls/more-info-alarm_control_panel";
import "../../../src/dialogs/more-info/controls/more-info-automation";
import "../../../src/dialogs/more-info/controls/more-info-camera";
import "../../../src/dialogs/more-info/controls/more-info-climate";
import "../../../src/dialogs/more-info/controls/more-info-configurator";
import "../../../src/dialogs/more-info/controls/more-info-counter";
import "../../../src/dialogs/more-info/controls/more-info-cover";
import "../../../src/dialogs/more-info/controls/more-info-default";
import "../../../src/dialogs/more-info/controls/more-info-fan";
import "../../../src/dialogs/more-info/controls/more-info-group";
import "../../../src/dialogs/more-info/controls/more-info-humidifier";
import "../../../src/dialogs/more-info/controls/more-info-input_datetime";
import "../../../src/dialogs/more-info/controls/more-info-light";
import "../../../src/dialogs/more-info/controls/more-info-lock";
import "../../../src/dialogs/more-info/controls/more-info-media_player";
import "../../../src/dialogs/more-info/controls/more-info-person";
import "../../../src/dialogs/more-info/controls/more-info-script";
import "../../../src/dialogs/more-info/controls/more-info-sun";
import "../../../src/dialogs/more-info/controls/more-info-timer";
import "../../../src/dialogs/more-info/controls/more-info-vacuum";
import "../../../src/dialogs/more-info/controls/more-info-water_heater";
import "../../../src/dialogs/more-info/controls/more-info-weather";
import { HomeAssistant } from "../../../src/types";
class MoreInfoContent extends UpdatingElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public stateObj?: HassEntity;
private _detachedChild?: ChildNode;
protected firstUpdated(): void {
this.style.position = "relative";
this.style.display = "block";
}
// This is not a lit element, but an updating element, so we implement update
protected update(changedProps: PropertyValues): void {
super.update(changedProps);
const stateObj = this.stateObj;
const hass = this.hass;
if (!stateObj || !hass) {
if (this.lastChild) {
this._detachedChild = this.lastChild;
// Detach child to prevent it from doing work.
this.removeChild(this.lastChild);
}
return;
}
if (this._detachedChild) {
this.appendChild(this._detachedChild);
this._detachedChild = undefined;
}
const moreInfoType =
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
? stateObj.attributes.custom_ui_more_info
: "more-info-" + stateMoreInfoType(stateObj);
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
hass,
stateObj,
});
}
}
customElements.define("more-info-content", MoreInfoContent);

View File

@@ -3,10 +3,10 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card";
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
import "../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-more-infos";
import "../components/more-info-content";
const ENTITIES = [
getEntity("light", "bed_light", "on", {

View File

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

View File

@@ -16,7 +16,6 @@ import "../../../src/components/ha-svg-icon";
import {
extractApiErrorMessage,
HassioResponse,
ignoredStatusCodes,
} from "../../../src/data/hassio/common";
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import {
@@ -165,9 +164,8 @@ export class HassioUpdate extends LitElement {
try {
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
} catch (err) {
// Only show an error if the status code was not expected (user behind proxy)
// or no status at all(connection terminated)
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
// Only show an error if the status code was not 504, or no status at all (connection terminated)
if (err.status_code && err.status_code !== 504) {
showAlertDialog(this, {
title: "Update failed",
text: extractApiErrorMessage(err),

View File

@@ -19,10 +19,7 @@ import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import {
extractApiErrorMessage,
ignoredStatusCodes,
} from "../../../src/data/hassio/common";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
changeHostOptions,
@@ -248,13 +245,10 @@ class HassioHostInfo extends LitElement {
try {
await rebootHost(this.hass);
} catch (err) {
// Ignore connection errors, these are all expected
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, {
title: "Failed to reboot",
text: extractApiErrorMessage(err),
});
}
showAlertDialog(this, {
title: "Failed to reboot",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
@@ -278,13 +272,10 @@ class HassioHostInfo extends LitElement {
try {
await shutdownHost(this.hass);
} catch (err) {
// Ignore connection errors, these are all expected
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, {
title: "Failed to shutdown",
text: extractApiErrorMessage(err),
});
}
showAlertDialog(this, {
title: "Failed to shutdown",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}

View File

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

View File

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

View File

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

View File

@@ -1,9 +0,0 @@
import { HomeAssistant } from "../../types";
/** Return an array of domains with the service. */
export const componentsWithService = (
hass: HomeAssistant,
service: string
): Array<string> =>
hass &&
Object.keys(hass.services).filter((key) => service in hass.services[key]);

View File

@@ -44,6 +44,7 @@ export const DOMAINS_WITH_MORE_INFO = [
"script",
"sun",
"timer",
"updater",
"vacuum",
"water_heater",
"weather",

View File

@@ -105,12 +105,12 @@ const processTheme = (
const keys = {};
for (const key of Object.keys(combinedTheme)) {
const prefixedKey = `--${key}`;
const value = String(combinedTheme[key]!);
const value = combinedTheme[key]!;
styles[prefixedKey] = value;
keys[prefixedKey] = "";
// Try to create a rgb value for this key if it is not a var
if (value.startsWith("#")) {
if (!value.startsWith("#")) {
// Can't convert non hex value
continue;
}

View File

@@ -3,51 +3,49 @@ import { HassEntity } from "home-assistant-js-websocket";
/** Return an icon representing a binary sensor state. */
export const binarySensorIcon = (state: HassEntity) => {
const is_off = state.state && state.state === "off";
const activated = state.state && state.state === "off";
switch (state.attributes.device_class) {
case "battery":
return is_off ? "hass:battery" : "hass:battery-outline";
case "battery_charging":
return is_off ? "hass:battery" : "hass:battery-charging";
return activated ? "hass:battery" : "hass:battery-outline";
case "cold":
return is_off ? "hass:thermometer" : "hass:snowflake";
return activated ? "hass:thermometer" : "hass:snowflake";
case "connectivity":
return is_off ? "hass:server-network-off" : "hass:server-network";
return activated ? "hass:server-network-off" : "hass:server-network";
case "door":
return is_off ? "hass:door-closed" : "hass:door-open";
return activated ? "hass:door-closed" : "hass:door-open";
case "garage_door":
return is_off ? "hass:garage" : "hass:garage-open";
return activated ? "hass:garage" : "hass:garage-open";
case "gas":
case "power":
case "problem":
case "safety":
case "smoke":
return is_off ? "hass:shield-check" : "hass:alert";
return activated ? "hass:shield-check" : "hass:alert";
case "heat":
return is_off ? "hass:thermometer" : "hass:fire";
return activated ? "hass:thermometer" : "hass:fire";
case "light":
return is_off ? "hass:brightness-5" : "hass:brightness-7";
return activated ? "hass:brightness-5" : "hass:brightness-7";
case "lock":
return is_off ? "hass:lock" : "hass:lock-open";
return activated ? "hass:lock" : "hass:lock-open";
case "moisture":
return is_off ? "hass:water-off" : "hass:water";
return activated ? "hass:water-off" : "hass:water";
case "motion":
return is_off ? "hass:walk" : "hass:run";
return activated ? "hass:walk" : "hass:run";
case "occupancy":
return is_off ? "hass:home-outline" : "hass:home";
return activated ? "hass:home-outline" : "hass:home";
case "opening":
return is_off ? "hass:square" : "hass:square-outline";
return activated ? "hass:square" : "hass:square-outline";
case "plug":
return is_off ? "hass:power-plug-off" : "hass:power-plug";
return activated ? "hass:power-plug-off" : "hass:power-plug";
case "presence":
return is_off ? "hass:home-outline" : "hass:home";
return activated ? "hass:home-outline" : "hass:home";
case "sound":
return is_off ? "hass:music-note-off" : "hass:music-note";
return activated ? "hass:music-note-off" : "hass:music-note";
case "vibration":
return is_off ? "hass:crop-portrait" : "hass:vibrate";
return activated ? "hass:crop-portrait" : "hass:vibrate";
case "window":
return is_off ? "hass:window-closed" : "hass:window-open";
return activated ? "hass:window-closed" : "hass:window-open";
default:
return is_off ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
return activated ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
}
};

View File

@@ -3,10 +3,9 @@ import { HomeAssistant } from "../../types";
import { DOMAINS_WITH_CARD } from "../const";
import { canToggleState } from "./can_toggle_state";
import { computeStateDomain } from "./compute_state_domain";
import { UNAVAILABLE } from "../../data/entity";
export const stateCardType = (hass: HomeAssistant, stateObj: HassEntity) => {
if (stateObj.state === UNAVAILABLE) {
if (stateObj.state === "unavailable") {
return "display";
}

View File

@@ -1,12 +1,7 @@
import { HassEntity } from "home-assistant-js-websocket";
import durationToSeconds from "../datetime/duration_to_seconds";
export const timerTimeRemaining = (
stateObj: HassEntity
): undefined | number => {
if (!stateObj.attributes.remaining) {
return undefined;
}
export const timerTimeRemaining = (stateObj: HassEntity) => {
let timeRemaining = durationToSeconds(stateObj.attributes.remaining);
if (stateObj.state === "active") {

View File

@@ -1,50 +0,0 @@
// From: underscore.js https://github.com/jashkenas/underscore/blob/master/underscore.js
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `false for leading`. To disable execution on the trailing edge, ditto.
export const throttle = <T extends Function>(
func: T,
wait: number,
leading = true,
trailing = true
): T => {
let timeout: number | undefined;
let previous = 0;
let context: any;
let args: any;
const later = () => {
previous = leading === false ? 0 : Date.now();
timeout = undefined;
func.apply(context, args);
if (!timeout) {
context = null;
args = null;
}
};
// @ts-ignore
return function (...argmnts) {
// @ts-ignore
// @typescript-eslint/no-this-alias
context = this;
args = argmnts;
const now = Date.now();
if (!previous && leading === false) {
previous = now;
}
const remaining = wait - (now - previous);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
previous = now;
func.apply(context, args);
} else if (!timeout && trailing !== false) {
timeout = window.setTimeout(later, remaining);
}
};
};

View File

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

View File

@@ -1,3 +1,4 @@
import "../ha-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
@@ -6,7 +7,6 @@ import { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
@@ -20,7 +20,6 @@ import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "./state-badge";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
@@ -52,8 +51,7 @@ const rowRenderer = (
root.querySelector("[secondary]")!.textContent = model.item.entity_id;
};
@customElement("ha-entity-picker")
export class HaEntityPicker extends LitElement {
class HaEntityPicker extends LitElement {
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled?: boolean;
@@ -97,8 +95,6 @@ export class HaEntityPicker extends LitElement {
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
private _initedStates = false;
private _getStates = memoizeOne(
(
_opened: boolean,
@@ -152,18 +148,11 @@ export class HaEntityPicker extends LitElement {
);
protected shouldUpdate(changedProps: PropertyValues) {
if (
changedProps.has("value") ||
changedProps.has("label") ||
changedProps.has("disabled")
) {
return true;
}
return !(!changedProps.has("_opened") && this._opened);
}
protected updated(changedProps: PropertyValues) {
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
if (changedProps.has("_opened") && this._opened) {
const states = this._getStates(
this._opened,
this.hass,
@@ -173,7 +162,6 @@ export class HaEntityPicker extends LitElement {
this.includeDeviceClasses
);
(this._comboBox as any).items = states;
this._initedStates = true;
}
}
@@ -181,6 +169,7 @@ export class HaEntityPicker extends LitElement {
if (!this.hass) {
return html``;
}
return html`
<vaadin-combo-box-light
item-value-path="entity_id"
@@ -278,6 +267,8 @@ export class HaEntityPicker extends LitElement {
}
}
customElements.define("ha-entity-picker", HaEntityPicker);
declare global {
interface HTMLElementTagNameMap {
"ha-entity-picker": HaEntityPicker;

View File

@@ -20,7 +20,6 @@ import { stateIcon } from "../../common/entity/state_icon";
import { timerTimeRemaining } from "../../common/entity/timer_time_remaining";
import { HomeAssistant } from "../../types";
import "../ha-label-badge";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
@customElement("ha-state-label-badge")
export class HaStateLabelBadge extends LitElement {
@@ -82,8 +81,7 @@ export class HaStateLabelBadge extends LitElement {
? ""
: this.image
? this.image
: state.attributes.entity_picture_local ||
state.attributes.entity_picture}"
: state.attributes.entity_picture_local || state.attributes.entity_picture}"
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
.description="${this.name ? this.name : computeStateName(state)}"
></ha-label-badge>
@@ -110,7 +108,7 @@ export class HaStateLabelBadge extends LitElement {
return null;
case "sensor":
default:
return state.state === UNKNOWN
return state.state === "unknown"
? "-"
: state.attributes.unit_of_measurement
? state.state
@@ -123,7 +121,7 @@ export class HaStateLabelBadge extends LitElement {
}
private _computeIcon(domain: string, state: HassEntity) {
if (state.state === UNAVAILABLE) {
if (state.state === "unavailable") {
return null;
}
switch (domain) {
@@ -168,7 +166,7 @@ export class HaStateLabelBadge extends LitElement {
private _computeLabel(domain, state, _timerTimeRemaining) {
if (
state.state === UNAVAILABLE ||
state.state === "unavailable" ||
["device_tracker", "alarm_control_panel", "person"].includes(domain)
) {
// Localize the state with a special state_badge namespace, which has variations of

View File

@@ -26,11 +26,7 @@ class HaCameraStream extends LitElement {
@property({ attribute: false }) public stateObj?: CameraEntity;
@property({ type: Boolean, attribute: "controls" })
public controls = false;
@property({ type: Boolean, attribute: "muted" })
public muted = false;
@property({ type: Boolean }) public showControls = false;
// We keep track if we should force MJPEG with a string
// that way it automatically resets if we change entity.
@@ -39,7 +35,7 @@ class HaCameraStream extends LitElement {
@internalProperty() private _url?: string;
protected render(): TemplateResult {
if (!this.stateObj) {
if (!this.stateObj || (!this._forceMJPEG && !this._url)) {
return html``;
}
@@ -56,23 +52,21 @@ class HaCameraStream extends LitElement {
)} camera.`}
/>
`
: this._url
? html`
: html`
<ha-hls-player
autoplay
muted
playsinline
.muted=${this.muted}
.controls=${this.controls}
?controls=${this.showControls}
.hass=${this.hass}
.url=${this._url}
.url=${this._url!}
></ha-hls-player>
`
: ""}
`}
`;
}
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("stateObj") && !this._shouldRenderMJPEG) {
if (changedProps.has("stateObj")) {
this._forceMJPEG = undefined;
this._getStreamUrl();
}

View File

@@ -97,7 +97,6 @@ export class HaCodeEditor extends UpdatingElement {
.CodeMirror {
height: var(--code-mirror-height, auto);
direction: var(--code-mirror-direction, ltr);
font-family: var(--code-font-family, monospace);
}
.CodeMirror-scroll {
max-height: var(--code-mirror-max-height, --code-mirror-height);

View File

@@ -176,11 +176,6 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
this.drawColorWheel();
this.drawMarker();
if (this.desiredHsColor) {
this.setMarkerOnColor(this.desiredHsColor);
this.applyColorToCanvas(this.desiredHsColor);
}
this.interactionLayer.addEventListener("mousedown", (ev) =>
this.onMouseDown(ev)
);
@@ -324,9 +319,6 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
// set marker position to the given color
setMarkerOnColor(hs) {
if (!this.marker || !this.tooltip) {
return;
}
const dist = hs.s * this.radius;
const theta = ((hs.h - 180) / 180) * Math.PI;
const markerdX = -dist * Math.cos(theta);
@@ -338,9 +330,6 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
// apply given color to interface elements
applyColorToCanvas(hs) {
if (!this.interactionLayer) {
return;
}
// we're not really converting hs to hsl here, but we keep it cheap
// setting the color on the interactionLayer, the svg elements can inherit
this.interactionLayer.style.color = `hsl(${hs.h}, 100%, ${

View File

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

View File

@@ -61,7 +61,7 @@ class HaHLSPlayer extends LitElement {
return html`
<video
?autoplay=${this.autoPlay}
.muted=${this.muted}
?muted=${this.muted}
?playsinline=${this.playsInline}
?controls=${this.controls}
@loadeddata=${this._elementResized}

View File

@@ -1,4 +1,3 @@
import "@polymer/paper-item/paper-item-body";
import {
css,
CSSResult,
@@ -8,6 +7,7 @@ import {
property,
SVGTemplateResult,
} from "lit-element";
import "@polymer/paper-item/paper-item-body";
@customElement("ha-settings-row")
export class HaSettingsRow extends LitElement {
@@ -49,9 +49,6 @@ export class HaSettingsRow extends LitElement {
border-top: 1px solid var(--divider-color);
padding-bottom: 8px;
}
::slotted(ha-switch) {
padding: 16px 0;
}
`;
}
}

View File

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

View File

@@ -23,6 +23,7 @@ import {
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { guard } from "lit-html/directives/guard";
@@ -159,13 +160,13 @@ const computePanels = memoizeOne(
let Sortable;
let sortStyles: CSSResult;
let sortStyles: TemplateResult;
@customElement("ha-sidebar")
class HaSidebar extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
@property() public narrow!: boolean;
@property({ type: Boolean }) public alwaysExpand = false;
@@ -227,21 +228,8 @@ class HaSidebar extends LitElement {
}
return html`
${this._editMode
? html`
<style>
${sortStyles?.cssText}
</style>
`
: ""}
<div
class="menu"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: !this._editMode,
disabled: this._editMode,
})}
>
${this._editMode ? sortStyles : ""}
<div class="menu">
${!this.narrow
? html`
<mwc-icon-button
@@ -259,7 +247,7 @@ class HaSidebar extends LitElement {
<div class="title">
${this._editMode
? html`<mwc-button outlined @click=${this._closeEditMode}>
${hass.localize("ui.sidebar.done")}
DONE
</mwc-button>`
: "Home Assistant"}
</div>
@@ -272,6 +260,11 @@ class HaSidebar extends LitElement {
@focusout=${this._listboxFocusOut}
@scroll=${this._listboxScroll}
@keydown=${this._listboxKeydown}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: !this._editMode,
disabled: this._editMode,
})}
>
${this._editMode
? html`<div id="sortable">
@@ -287,29 +280,27 @@ class HaSidebar extends LitElement {
? html`
${this._hiddenPanels.map((url) => {
const panel = this.hass.panels[url];
if (!panel) {
return "";
}
return html`<paper-icon-item
@click=${this._unhidePanel}
class="hidden-panel"
.panel=${url}
>
<ha-icon
slot="item-icon"
.icon=${panel.url_path === this.hass.defaultPanel
.icon=${panel.url_path === "lovelace"
? "mdi:view-dashboard"
: panel.icon}
></ha-icon>
<span class="item-text"
>${panel.url_path === this.hass.defaultPanel
>${panel.url_path === "lovelace"
? hass.localize("panel.states")
: hass.localize(`panel.${panel.title}`) ||
panel.title}</span
>
<mwc-icon-button class="hide-panel">
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
</mwc-icon-button>
<ha-svg-icon
class="hide-panel"
.panel=${url}
.path=${mdiPlus}
></ha-svg-icon>
</paper-icon-item>`;
})}
<div class="spacer" disabled></div>
@@ -449,9 +440,6 @@ class HaSidebar extends LitElement {
subscribeNotifications(this.hass.connection, (notifications) => {
this._notifications = notifications;
});
window.addEventListener("hass-edit-sidebar", () =>
this._activateEditMode()
);
}
protected updated(changedProps) {
@@ -484,22 +472,18 @@ class HaSidebar extends LitElement {
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
}
private _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
private async _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
if (ev.detail.action !== "hold") {
return;
}
this._activateEditMode();
}
private async _activateEditMode() {
if (!Sortable) {
const [sortableImport, sortStylesImport] = await Promise.all([
import("sortablejs/modular/sortable.core.esm"),
import("../resources/ha-sortable-style"),
import("./ha-sidebar-sort-styles"),
]);
sortStyles = sortStylesImport.sortableStyles;
sortStyles = sortStylesImport.sortStyles;
Sortable = sortableImport.Sortable;
Sortable.mount(sortableImport.OnSpill);
@@ -507,8 +491,6 @@ class HaSidebar extends LitElement {
}
this._editMode = true;
fireEvent(this, "hass-open-menu");
await this.updateComplete;
this._createSortable();
@@ -533,7 +515,7 @@ class HaSidebar extends LitElement {
private async _hidePanel(ev: Event) {
ev.preventDefault();
const panel = (ev.currentTarget as any).panel;
const panel = (ev.target as any).panel;
if (this._hiddenPanels.includes(panel)) {
return;
}
@@ -546,7 +528,7 @@ class HaSidebar extends LitElement {
private async _unhidePanel(ev: Event) {
ev.preventDefault();
const index = this._hiddenPanels.indexOf((ev.currentTarget as any).panel);
const index = this._hiddenPanels.indexOf((ev.target as any).panel);
if (index < 0) {
return;
}
@@ -658,13 +640,11 @@ class HaSidebar extends LitElement {
return panels.map((panel) =>
this._renderPanel(
panel.url_path,
panel.url_path === this.hass.defaultPanel
? panel.title || this.hass.localize("panel.states")
panel.url_path === "lovelace"
? this.hass.localize("panel.states")
: this.hass.localize(`panel.${panel.title}`) || panel.title,
panel.icon,
panel.url_path === this.hass.defaultPanel && !panel.icon
? mdiViewDashboard
: undefined
panel.url_path === "lovelace" ? undefined : panel.icon,
panel.url_path === "lovelace" ? mdiViewDashboard : undefined
)
);
}
@@ -678,8 +658,8 @@ class HaSidebar extends LitElement {
return html`
<a
aria-role="option"
href=${`/${urlPath}`}
data-panel=${urlPath}
href="${`/${urlPath}`}"
data-panel="${urlPath}"
tabindex="-1"
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
@@ -693,13 +673,12 @@ class HaSidebar extends LitElement {
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
<span class="item-text">${title}</span>
${this._editMode
? html`<mwc-icon-button
? html`<ha-svg-icon
class="hide-panel"
.panel=${urlPath}
@click=${this._hidePanel}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>`
.path=${mdiClose}
></ha-svg-icon>`
: ""}
</paper-icon-item>
</a>
@@ -768,9 +747,6 @@ class HaSidebar extends LitElement {
width: 100%;
display: none;
}
:host([narrow]) .title {
padding: 0 16px;
}
:host([expanded]) .title {
display: initial;
}

View File

@@ -279,7 +279,6 @@ class LocationEditor extends LitElement {
}
#map {
height: 100%;
background: inherit;
}
.leaflet-edit-move {
border-radius: 50%;

View File

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

View File

@@ -2,7 +2,7 @@ import "@material/mwc-button/mwc-button";
import "@material/mwc-fab/mwc-fab";
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js";
import { mdiArrowLeft, mdiClose, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
@@ -18,7 +18,7 @@ import {
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { ifDefined } from "lit-html/directives/if-defined";
import { styleMap } from "lit-html/directives/style-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { computeRTLDirection } from "../../common/util/compute_rtl";
import { debounce } from "../../common/util/debounce";
@@ -26,12 +26,10 @@ import {
browseLocalMediaPlayer,
browseMediaPlayer,
BROWSER_SOURCE,
MediaClassBrowserSettings,
MediaPickedEvent,
MediaPlayerBrowseAction,
} from "../../data/media-player";
import type { MediaPlayerItem } from "../../data/media-player";
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
@@ -67,8 +65,6 @@ export class HaMediaPlayerBrowse extends LitElement {
@internalProperty() private _loading = false;
@internalProperty() private _error?: { message: string; code: string };
@internalProperty() private _mediaPlayerItems: MediaPlayerItem[] = [];
private _resizeObserver?: ResizeObserver;
@@ -98,22 +94,6 @@ export class HaMediaPlayerBrowse extends LitElement {
return html`<ha-circular-progress active></ha-circular-progress>`;
}
if (this._error && !this._mediaPlayerItems.length) {
if (this.dialog) {
this._closeDialogAction();
showAlertDialog(this, {
title: this.hass.localize(
"ui.components.media-browser.media_browsing_error"
),
text: this._renderError(this._error),
});
} else {
return html`<div class="container">
${this._renderError(this._error)}
</div>`;
}
}
if (!this._mediaPlayerItems.length) {
return html``;
}
@@ -127,152 +107,129 @@ export class HaMediaPlayerBrowse extends LitElement {
? this._mediaPlayerItems[this._mediaPlayerItems.length - 2]
: undefined;
const subtitle = this.hass.localize(
`ui.components.media-browser.class.${currentItem.media_class}`
const hasExpandableChildren:
| MediaPlayerItem
| undefined = this._hasExpandableChildren(currentItem.children);
const showImages: boolean | undefined = currentItem.children?.some(
(child) => child.thumbnail && child.thumbnail !== currentItem.thumbnail
);
const mediaType = this.hass.localize(
`ui.components.media-browser.content-type.${currentItem.media_content_type}`
);
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
const childrenMediaClass =
MediaClassBrowserSettings[currentItem.children_media_class];
return html`
<div
class="header ${classMap({
"no-img": !currentItem.thumbnail,
"no-dialog": !this.dialog,
})}"
>
<div class="header-wrapper">
<div class="header-content">
${currentItem.thumbnail
? html`
<div
class="img"
style=${styleMap({
backgroundImage: currentItem.thumbnail
? `url(${currentItem.thumbnail})`
: "none",
})}
>
${this._narrow && currentItem?.can_play
? html`
<mwc-fab
mini
.item=${currentItem}
@click=${this._actionClicked}
>
<ha-svg-icon
slot="icon"
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
.path=${this.action === "play"
? mdiPlay
: mdiPlus}
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.${this.action}`
<div class="header-content">
${currentItem.thumbnail
? html`
<div
class="img"
style="background-image: url(${currentItem.thumbnail})"
>
${this._narrow && currentItem?.can_play
? html`
<mwc-fab
mini
.item=${currentItem}
@click=${this._actionClicked}
>
<ha-svg-icon
slot="icon"
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
</mwc-fab>
`
: ""}
</div>
`
: html``}
<div class="header-info">
<div class="breadcrumb">
${previousItem
? html`
<div class="previous-title" @click=${this.navigateBack}>
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
${previousItem.title}
</div>
`
: ""}
<h1 class="title">${currentItem.title}</h1>
${subtitle
? html`
<h2 class="subtitle">
${subtitle}
</h2>
`
: ""}
</div>
${currentItem.can_play &&
(!currentItem.thumbnail || !this._narrow)
.path=${this.action === "play" ? mdiPlay : mdiPlus}
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.${this.action}`
)}
</mwc-fab>
`
: ""}
</div>
`
: html``}
<div class="header-info">
<div class="breadcrumb">
${previousItem
? html`
<mwc-button
raised
.item=${currentItem}
@click=${this._actionClicked}
>
<ha-svg-icon
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
.path=${this.action === "play" ? mdiPlay : mdiPlus}
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.${this.action}`
)}
</mwc-button>
<div class="previous-title" @click=${this.navigateBack}>
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
${previousItem.title}
</div>
`
: ""}
<h1 class="title">${currentItem.title}</h1>
${mediaType
? html`
<h2 class="subtitle">
${mediaType}
</h2>
`
: ""}
</div>
${currentItem.can_play && (!currentItem.thumbnail || !this._narrow)
? html`
<mwc-button
raised
.item=${currentItem}
@click=${this._actionClicked}
>
<ha-svg-icon
slot="icon"
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
.path=${this.action === "play" ? mdiPlay : mdiPlus}
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.${this.action}`
)}
</mwc-button>
`
: ""}
</div>
${this.dialog
? html`
<mwc-icon-button
aria-label=${this.hass.localize("ui.dialogs.generic.close")}
@click=${this._closeDialogAction}
class="header_button"
dir=${computeRTLDirection(this.hass)}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
`
: ""}
</div>
</div>
${this._error
? html`
<div class="container error">
${this._renderError(this._error)}
</div>
`
: currentItem.children?.length
? childrenMediaClass.layout === "grid"
${this.dialog
? html`
<div
class="children ${classMap({
portrait: childrenMediaClass.thumbnail_ratio === "portrait",
})}"
<mwc-icon-button
aria-label=${this.hass.localize("ui.dialogs.generic.close")}
@click=${this._closeDialogAction}
class="header_button"
dir=${computeRTLDirection(this.hass)}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
`
: ""}
</div>
${currentItem.children?.length
? hasExpandableChildren
? html`
<div class="children">
${currentItem.children.map(
(child) => html`
<div
class="child"
.item=${child}
@click=${this._childClicked}
@click=${this._navigateForward}
>
<div class="ha-card-parent">
<ha-card
outlined
style=${styleMap({
backgroundImage: child.thumbnail
? `url(${child.thumbnail})`
: "none",
})}
style="background-image: url(${child.thumbnail})"
>
${!child.thumbnail
${child.can_expand && !child.thumbnail
? html`
<ha-svg-icon
class="folder"
.path=${MediaClassBrowserSettings[
child.media_class === "directory"
? child.children_media_class ||
child.media_class
: child.media_class
].icon}
.path=${mdiFolder}
></ha-svg-icon>
`
: ""}
@@ -280,9 +237,7 @@ export class HaMediaPlayerBrowse extends LitElement {
${child.can_play
? html`
<mwc-icon-button
class="play ${classMap({
can_expand: child.can_expand,
})}"
class="play"
.item=${child}
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
@@ -314,16 +269,15 @@ export class HaMediaPlayerBrowse extends LitElement {
${currentItem.children.map(
(child) => html`
<mwc-list-item
@click=${this._childClicked}
@click=${this._actionClicked}
.item=${child}
graphic="avatar"
hasMeta
dir=${computeRTLDirection(this.hass)}
>
<div
class="graphic"
style=${ifDefined(
mediaClass.show_list_images && child.thumbnail
showImages && child.thumbnail
? `background-image: url(${child.thumbnail})`
: undefined
)}
@@ -331,8 +285,7 @@ export class HaMediaPlayerBrowse extends LitElement {
>
<mwc-icon-button
class="play ${classMap({
show:
!mediaClass.show_list_images || !child.thumbnail,
show: !showImages || !child.thumbnail,
})}"
.item=${child}
.label=${this.hass.localize(
@@ -345,18 +298,14 @@ export class HaMediaPlayerBrowse extends LitElement {
></ha-svg-icon>
</mwc-icon-button>
</div>
<span class="title">${child.title}</span>
<span>${child.title}</span>
</mwc-list-item>
<li divider role="separator"></li>
`
)}
</mwc-list>
`
: html`
<div class="container">
${this.hass.localize("ui.components.media-browser.no_items")}
</div>
`}
: this.hass.localize("ui.components.media-browser.no_items")}
`;
}
@@ -382,22 +331,11 @@ export class HaMediaPlayerBrowse extends LitElement {
return;
}
if (changedProps.has("entityId")) {
this._error = undefined;
this._mediaPlayerItems = [];
}
this._fetchData(this.mediaContentId, this.mediaContentType)
.then((itemData) => {
if (!itemData) {
return;
}
this._fetchData(this.mediaContentId, this.mediaContentType).then(
(itemData) => {
this._mediaPlayerItems = [itemData];
})
.catch((err) => {
this._error = err;
});
}
);
}
private _actionClicked(ev: MouseEvent): void {
@@ -411,41 +349,21 @@ export class HaMediaPlayerBrowse extends LitElement {
fireEvent(this, "media-picked", { item });
}
private async _childClicked(ev: MouseEvent): Promise<void> {
private async _navigateForward(ev: MouseEvent): Promise<void> {
const target = ev.currentTarget as any;
const item: MediaPlayerItem = target.item;
if (!item) {
return;
}
if (!item.can_expand) {
this._runAction(item);
return;
}
this._navigate(item);
}
private async _navigate(item: MediaPlayerItem) {
this._error = undefined;
let itemData: MediaPlayerItem;
try {
itemData = await this._fetchData(
item.media_content_id,
item.media_content_type
);
} catch (err) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.components.media-browser.media_browsing_error"
),
text: this._renderError(err),
});
return;
}
const itemData = await this._fetchData(
item.media_content_id,
item.media_content_type
);
this.scrollTo(0, 0);
this._mediaPlayerItems = [...this._mediaPlayerItems, itemData];
@@ -491,38 +409,14 @@ export class HaMediaPlayerBrowse extends LitElement {
this._resizeObserver.observe(this);
}
private _hasExpandableChildren = memoizeOne((children) =>
children.find((item: MediaPlayerItem) => item.can_expand)
);
private _closeDialogAction(): void {
fireEvent(this, "close-dialog");
}
private _renderError(err: { message: string; code: string }) {
if (err.message === "Media directory does not exist.") {
return html`
<h2>No local media found.</h2>
<p>
It looks like you have not yet created a media directory.
<br />Create a directory with the name <b>"media"</b> in the
configuration directory of Home Assistant
(${this.hass.config.config_dir}). <br />Place your video, audio and
image files in this directory to be able to browse and play them in
the browser or on supported media players.
</p>
<p>
Check the
<a
href="https://www.home-assistant.io/integrations/media_source/#local-media"
target="_blank"
rel="noreferrer"
>documentation</a
>
for more info
</p>
`;
}
return html`<span class="error">err.message</span>`;
}
static get styles(): CSSResultArray {
return [
haStyle,
@@ -535,26 +429,26 @@ export class HaMediaPlayerBrowse extends LitElement {
flex-direction: column;
}
.container {
padding: 16px;
.header {
display: flex;
justify-content: space-between;
border-bottom: 1px solid var(--divider-color);
}
.header_button {
position: relative;
top: 14px;
right: -8px;
}
.header {
display: block;
justify-content: space-between;
border-bottom: 1px solid var(--divider-color);
background-color: var(--card-background-color);
position: sticky;
position: -webkit-sticky;
top: 0;
z-index: 5;
padding: 20px 24px 10px;
}
.header-wrapper {
display: flex;
}
.header-content {
display: flex;
flex-wrap: wrap;
@@ -582,7 +476,6 @@ export class HaMediaPlayerBrowse extends LitElement {
.header-info mwc-button {
display: block;
--mdc-theme-primary: var(--primary-color);
}
.breadcrumb {
@@ -626,7 +519,6 @@ export class HaMediaPlayerBrowse extends LitElement {
mwc-list {
--mdc-list-vertical-padding: 0;
--mdc-list-item-graphic-margin: 0;
--mdc-theme-text-icon-on-background: var(--secondary-text-color);
margin-top: 10px;
}
@@ -643,18 +535,14 @@ export class HaMediaPlayerBrowse extends LitElement {
display: grid;
grid-template-columns: repeat(
auto-fit,
minmax(var(--media-browse-item-size, 175px), 0.1fr)
minmax(var(--media-browse-item-size, 175px), 0.33fr)
);
grid-gap: 16px;
margin: 8px 0px;
padding: 0px 24px;
}
:host([dialog]) .children {
grid-template-columns: repeat(
auto-fit,
minmax(var(--media-browse-item-size, 175px), 0.33fr)
);
:host(:not([narrow])) .children {
padding: 0px 24px;
}
.child {
@@ -668,7 +556,7 @@ export class HaMediaPlayerBrowse extends LitElement {
width: 100%;
}
.children ha-card {
ha-card {
width: 100%;
padding-bottom: 100%;
position: relative;
@@ -676,11 +564,6 @@ export class HaMediaPlayerBrowse extends LitElement {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
transition: padding-bottom 0.1s ease-out;
}
.portrait.children ha-card {
padding-bottom: 150%;
}
.child .folder,
@@ -696,43 +579,24 @@ export class HaMediaPlayerBrowse extends LitElement {
}
.child .play {
transition: color 0.5s;
border-radius: 50%;
bottom: calc(50% - 35px);
right: calc(50% - 35px);
opacity: 0;
transition: opacity 0.1s ease-out;
}
.child .play:not(.can_expand) {
--mdc-icon-button-size: 70px;
--mdc-icon-size: 48px;
}
.ha-card-parent:hover .play:not(.can_expand) {
opacity: 1;
color: var(--primary-color);
}
.child .play.can_expand {
opacity: 1;
background-color: rgba(var(--rgb-card-background-color), 0.5);
bottom: 4px;
right: 4px;
transition: all 0.5s;
background-color: rgba(var(--rgb-card-background-color), 0.5);
border-radius: 50%;
}
.child .play:hover {
color: var(--primary-color);
}
.ha-card-parent:hover ha-card {
ha-card:hover {
opacity: 0.5;
}
.child .title {
font-size: 16px;
padding-top: 8px;
padding-left: 2px;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
@@ -742,7 +606,6 @@ export class HaMediaPlayerBrowse extends LitElement {
.child .type {
font-size: 12px;
color: var(--secondary-text-color);
padding-left: 2px;
}
mwc-list-item .graphic {
@@ -767,14 +630,6 @@ export class HaMediaPlayerBrowse extends LitElement {
background-color: transparent;
}
mwc-list-item .title {
margin-left: 16px;
}
mwc-list-item[dir="rtl"] .title {
margin-right: 16px;
margin-left: 0;
}
/* ============= Narrow ============= */
:host([narrow]) {
@@ -789,10 +644,6 @@ export class HaMediaPlayerBrowse extends LitElement {
padding: 0;
}
:host([narrow]) .header.no-dialog {
display: block;
}
:host([narrow]) .header_button {
position: absolute;
top: 14px;
@@ -832,7 +683,8 @@ export class HaMediaPlayerBrowse extends LitElement {
padding: 20px 24px 10px;
}
:host([narrow]) .media-source {
:host([narrow]) .media-source,
:host([narrow]) .children {
padding: 0 24px;
}
@@ -851,8 +703,8 @@ export class HaMediaPlayerBrowse extends LitElement {
-webkit-line-clamp: 1;
}
:host(:not([narrow])[scroll]) .header:not(.no-img) mwc-icon-button {
align-self: center;
:host(:not([narrow])[scroll]) .header-info {
height: 75px;
}
:host([scroll]) .header-info mwc-button,

View File

@@ -39,9 +39,6 @@ class PersonBadge extends LitElement {
static get styles(): CSSResult {
return css`
:host {
display: contents;
}
.picture {
width: 40px;
height: 40px;

View File

@@ -104,9 +104,6 @@ class UserBadge extends LitElement {
static get styles(): CSSResult {
return css`
:host {
display: contents;
}
.picture {
width: 40px;
height: 40px;

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,5 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../types";
import {
mdiFolder,
mdiPlaylistMusic,
mdiFileMusic,
mdiAlbum,
mdiMusic,
mdiTelevisionClassic,
mdiMovie,
mdiVideo,
mdiImage,
mdiWeb,
mdiGamepadVariant,
mdiAccountMusic,
mdiPodcast,
mdiApplication,
mdiAccountMusicOutline,
mdiDramaMasks,
} from "@mdi/js";
export const SUPPORT_PAUSE = 1;
export const SUPPORT_SEEK = 2;
@@ -40,66 +22,6 @@ export type MediaPlayerBrowseAction = "pick" | "play";
export const BROWSER_SOURCE = "browser";
export type MediaClassBrowserSetting = {
icon: string;
thumbnail_ratio?: string;
layout?: string;
show_list_images?: boolean;
};
export const MediaClassBrowserSettings: {
[type: string]: MediaClassBrowserSetting;
} = {
album: { icon: mdiAlbum, layout: "grid" },
app: { icon: mdiApplication, layout: "grid" },
artist: { icon: mdiAccountMusic, layout: "grid", show_list_images: true },
channel: {
icon: mdiTelevisionClassic,
thumbnail_ratio: "portrait",
layout: "grid",
},
composer: {
icon: mdiAccountMusicOutline,
layout: "grid",
show_list_images: true,
},
contributing_artist: {
icon: mdiAccountMusic,
layout: "grid",
show_list_images: true,
},
directory: { icon: mdiFolder, layout: "grid", show_list_images: true },
episode: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait",
},
game: {
icon: mdiGamepadVariant,
layout: "grid",
thumbnail_ratio: "portrait",
},
genre: { icon: mdiDramaMasks, layout: "grid", show_list_images: true },
image: { icon: mdiImage, layout: "grid" },
movie: { icon: mdiMovie, thumbnail_ratio: "portrait", layout: "grid" },
music: { icon: mdiMusic },
playlist: { icon: mdiPlaylistMusic, layout: "grid", show_list_images: true },
podcast: { icon: mdiPodcast, layout: "grid" },
season: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait",
},
track: { icon: mdiFileMusic },
tv_show: {
icon: mdiTelevisionClassic,
layout: "grid",
thumbnail_ratio: "portrait",
},
url: { icon: mdiWeb },
video: { icon: mdiVideo, layout: "grid" },
};
export interface MediaPickedEvent {
item: MediaPlayerItem;
}
@@ -118,8 +40,6 @@ export interface MediaPlayerItem {
title: string;
media_content_type: string;
media_content_id: string;
media_class: string;
children_media_class: string;
can_play: boolean;
can_expand: boolean;
thumbnail?: string;

View File

@@ -1,17 +0,0 @@
declare global {
interface HASSDomEvents {
"hass-refresh-tokens": undefined;
}
}
export interface RefreshToken {
client_icon?: string;
client_id: string;
client_name?: string;
created_at: string;
id: string;
is_current: boolean;
last_used_at?: string;
last_used_ip?: string;
type: "normal" | "long_lived_access_token";
}

View File

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

View File

@@ -200,7 +200,7 @@ export const weatherSVGStyles = css`
fill: var(--weather-icon-sun-color, #fdd93c);
}
.moon {
fill: var(--weather-icon-moon-color, #fcf497);
fill: var(--weather-icon-moon-color, #fdf9cc);
}
.cloud-back {
fill: var(--weather-icon-cloud-back-color, #d4d4d4);

View File

@@ -1,27 +1,20 @@
import { Connection, UnsubscribeFunc } from "home-assistant-js-websocket";
export interface RenderTemplateResult {
interface RenderTemplateResult {
result: string;
listeners: TemplateListeners;
}
interface TemplateListeners {
all: boolean;
domains: string[];
entities: string[];
}
export const subscribeRenderTemplate = (
conn: Connection,
onChange: (result: RenderTemplateResult) => void,
onChange: (result: string) => void,
params: {
template: string;
entity_ids?: string | string[];
variables?: object;
}
): Promise<UnsubscribeFunc> => {
return conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), {
type: "render_template",
...params,
});
return conn.subscribeMessage(
(msg: RenderTemplateResult) => onChange(msg.result),
{ type: "render_template", ...params }
);
};

View File

@@ -97,13 +97,8 @@ export const showConfigFlowDialog = (
},
renderExternalStepHeader(hass, step) {
return (
hass.localize(
`component.${step.handler}.config.step.${step.step_id}.title`
) ||
hass.localize(
"ui.panel.config.integrations.config_flow.external_step.open_site"
)
return hass.localize(
`component.${step.handler}.config.step.${step.step_id}.title`
);
},

View File

@@ -5,19 +5,19 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-dialog";
import "../../components/ha-switch";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { DialogParams } from "./show-dialog-box";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("dialog-box")
class DialogBox extends LitElement {
@@ -57,8 +57,7 @@ class DialogBox extends LitElement {
open
?scrimClickAction=${this._params.prompt}
?escapeKeyAction=${this._params.prompt}
@closed=${this._dialogClosed}
defaultAction="ignore"
@closed=${this._dismiss}
.heading=${this._params.title
? this._params.title
: this._params.confirmation &&
@@ -79,10 +78,10 @@ class DialogBox extends LitElement {
${this._params.prompt
? html`
<paper-input
dialogInitialFocus
autofocus
.value=${this._value}
@keyup=${this._handleKeyUp}
@value-changed=${this._valueChanged}
@keyup=${this._handleKeyUp}
.label=${this._params.inputLabel
? this._params.inputLabel
: ""}
@@ -101,11 +100,7 @@ class DialogBox extends LitElement {
: this.hass.localize("ui.dialogs.generic.cancel")}
</mwc-button>
`}
<mwc-button
@click=${this._confirm}
?dialogInitialFocus=${!this._params.prompt}
slot="primaryAction"
>
<mwc-button @click=${this._confirm} slot="primaryAction">
${this._params.confirmText
? this._params.confirmText
: this.hass.localize("ui.dialogs.generic.ok")}
@@ -119,8 +114,8 @@ class DialogBox extends LitElement {
}
private _dismiss(): void {
if (this._params?.cancel) {
this._params.cancel();
if (this._params!.cancel) {
this._params!.cancel();
}
this._close();
}
@@ -138,17 +133,7 @@ class DialogBox extends LitElement {
this._close();
}
private _dialogClosed(ev) {
if (ev.detail.action === "ignore") {
return;
}
this.closeDialog();
}
private _close(): void {
if (!this._params) {
return;
}
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}

View File

@@ -12,13 +12,12 @@ import {
import "../../../components/ha-relative-time";
import { triggerAutomation } from "../../../data/automation";
import { HomeAssistant } from "../../../types";
import { UNAVAILABLE_STATES } from "../../../data/entity";
@customElement("more-info-automation")
class MoreInfoAutomation extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj?: HassEntity;
@property() public stateObj?: HassEntity;
protected render(): TemplateResult {
if (!this.hass || !this.stateObj) {
@@ -35,10 +34,7 @@ class MoreInfoAutomation extends LitElement {
</div>
<div class="actions">
<mwc-button
@click=${this.handleAction}
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj!.state)}
>
<mwc-button @click=${this.handleAction}>
${this.hass.localize("ui.card.automation.trigger")}
</mwc-button>
</div>
@@ -56,7 +52,7 @@ class MoreInfoAutomation extends LitElement {
justify-content: space-between;
}
.actions {
margin: 8px 0;
margin: 36px 0 8px 0;
text-align: right;
}
`;

View File

@@ -4,9 +4,9 @@ import {
css,
CSSResult,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -47,8 +47,8 @@ class MoreInfoCamera extends LitElement {
return html`
<ha-camera-stream
.hass=${this.hass}
.stateObj=${this.stateObj}
controls
.stateObj="${this.stateObj}"
showcontrols
></ha-camera-stream>
${this._cameraPrefs
? html`

View File

@@ -61,20 +61,20 @@ class MoreInfoLight extends LitElement {
"is-on": this.stateObj.state === "on",
})}"
>
${supportsFeature(this.stateObj!, SUPPORT_BRIGHTNESS)
? html`
<ha-labeled-slider
caption=${this.hass.localize("ui.card.light.brightness")}
icon="hass:brightness-5"
min="1"
max="255"
value=${this._brightnessSliderValue}
@change=${this._brightnessSliderChanged}
></ha-labeled-slider>
`
: ""}
${this.stateObj.state === "on"
? html`
${supportsFeature(this.stateObj!, SUPPORT_BRIGHTNESS)
? html`
<ha-labeled-slider
caption=${this.hass.localize("ui.card.light.brightness")}
icon="hass:brightness-5"
min="1"
max="255"
value=${this._brightnessSliderValue}
@change=${this._brightnessSliderChanged}
></ha-labeled-slider>
`
: ""}
${supportsFeature(this.stateObj, SUPPORT_COLOR_TEMP)
? html`
<ha-labeled-slider
@@ -134,7 +134,7 @@ class MoreInfoLight extends LitElement {
attr-for-selected="item-name"
>${this.stateObj.attributes.effect_list.map(
(effect: string) => html`
<paper-item .itemName=${effect}
<paper-item itemName=${effect}
>${effect}</paper-item
>
`
@@ -170,7 +170,7 @@ class MoreInfoLight extends LitElement {
}
private _effectChanged(ev: CustomEvent) {
const newVal = ev.detail.item.itemName;
const newVal = ev.detail.value;
if (!newVal || this.stateObj!.attributes.effect === newVal) {
return;

View File

@@ -130,7 +130,7 @@ class MoreInfoMediaPlayer extends LitElement {
</div>
`
: ""}
${![UNAVAILABLE, UNKNOWN, "off"].includes(stateObj.state) &&
${stateObj.state !== "off" &&
supportsFeature(stateObj, SUPPORT_SELECT_SOURCE) &&
stateObj.attributes.source_list?.length
? html`
@@ -188,17 +188,14 @@ class MoreInfoMediaPlayer extends LitElement {
<div class="tts">
<paper-input
id="ttsInput"
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.label=${this.hass.localize(
"ui.card.media_player.text_to_speak"
)}
@keydown=${this._ttsCheckForEnter}
></paper-input>
<ha-icon-button
icon="hass:send"
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
@click=${this._sendTTS}
></ha-icon-button>
<ha-icon-button icon="hass:send" @click=${
this._sendTTS
}></ha-icon-button>
</div>
</div>
`

View File

@@ -26,12 +26,15 @@ class MoreInfoTimer extends LitElement {
return html`
<ha-attributes
.stateObj=${this.stateObj}
extra-filters="remaining"
.extraFilters=${"remaining"}
></ha-attributes>
<div class="actions">
${this.stateObj.state === "idle" || this.stateObj.state === "paused"
? html`
<mwc-button .action=${"start"} @click=${this._handleActionClick}>
<mwc-button
.action="${"start"}"
@click="${this._handleActionClick}"
>
${this.hass!.localize("ui.card.timer.actions.start")}
</mwc-button>
`
@@ -39,7 +42,7 @@ class MoreInfoTimer extends LitElement {
${this.stateObj.state === "active"
? html`
<mwc-button
.action=${"pause"}
.action="${"pause"}"
@click="${this._handleActionClick}"
>
${this.hass!.localize("ui.card.timer.actions.pause")}
@@ -49,13 +52,13 @@ class MoreInfoTimer extends LitElement {
${this.stateObj.state === "active" || this.stateObj.state === "paused"
? html`
<mwc-button
.action=${"cancel"}
.action="${"cancel"}"
@click="${this._handleActionClick}"
>
${this.hass!.localize("ui.card.timer.actions.cancel")}
</mwc-button>
<mwc-button
.action=${"finish"}
.action="${"finish"}"
@click="${this._handleActionClick}"
>
${this.hass!.localize("ui.card.timer.actions.finish")}

View File

@@ -13,15 +13,10 @@ import {
} from "lit-element";
import { cache } from "lit-html/directives/cache";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import {
DOMAINS_MORE_INFO_NO_HISTORY,
DOMAINS_WITH_MORE_INFO,
} from "../../common/const";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { stateMoreInfoType } from "../../common/entity/state_more_info_type";
import { navigate } from "../../common/navigate";
import "../../components/ha-dialog";
import "../../components/ha-header-bar";
@@ -34,39 +29,12 @@ import { haStyleDialog } from "../../resources/styles";
import "../../state-summary/state-card-content";
import { HomeAssistant } from "../../types";
import { showConfirmationDialog } from "../generic/show-dialog-box";
import "./ha-more-info-history";
import "./ha-more-info-logbook";
import "./more-info-content";
const DOMAINS_NO_INFO = ["camera", "configurator"];
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
const EDITABLE_DOMAINS = ["script"];
const MORE_INFO_CONTROL_IMPORT = {
alarm_control_panel: () => import("./controls/more-info-alarm_control_panel"),
automation: () => import("./controls/more-info-automation"),
camera: () => import("./controls/more-info-camera"),
climate: () => import("./controls/more-info-climate"),
configurator: () => import("./controls/more-info-configurator"),
counter: () => import("./controls/more-info-counter"),
cover: () => import("./controls/more-info-cover"),
fan: () => import("./controls/more-info-fan"),
group: () => import("./controls/more-info-group"),
humidifier: () => import("./controls/more-info-humidifier"),
input_datetime: () => import("./controls/more-info-input_datetime"),
light: () => import("./controls/more-info-light"),
lock: () => import("./controls/more-info-lock"),
media_player: () => import("./controls/more-info-media_player"),
person: () => import("./controls/more-info-person"),
script: () => import("./controls/more-info-script"),
sun: () => import("./controls/more-info-sun"),
timer: () => import("./controls/more-info-timer"),
vacuum: () => import("./controls/more-info-vacuum"),
water_heater: () => import("./controls/more-info-water_heater"),
weather: () => import("./controls/more-info-weather"),
hidden: () => {},
default: () => import("./controls/more-info-default"),
};
export interface MoreInfoDialogParams {
entityId: string | null;
}
@@ -79,8 +47,6 @@ export class MoreInfoDialog extends LitElement {
@internalProperty() private _entityId?: string | null;
@internalProperty() private _moreInfoType?: string;
@internalProperty() private _currTabIndex = 0;
public showDialog(params: MoreInfoDialogParams) {
@@ -97,23 +63,6 @@ export class MoreInfoDialog extends LitElement {
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected updated(changedProperties) {
if (!this.hass || !this._entityId || !changedProperties.has("_entityId")) {
return;
}
const stateObj = this.hass.states[this._entityId];
if (!stateObj) {
return;
}
if (stateObj.attributes && "custom_ui_more_info" in stateObj.attributes) {
this._moreInfoType = stateObj.attributes.custom_ui_more_info;
} else {
const type = stateMoreInfoType(stateObj);
this._moreInfoType = `more-info-${type}`;
MORE_INFO_CONTROL_IMPORT[type]();
}
}
protected render() {
if (!this._entityId) {
return html``;
@@ -178,8 +127,7 @@ export class MoreInfoDialog extends LitElement {
`
: ""}
</ha-header-bar>
${DOMAINS_WITH_MORE_INFO.includes(domain) &&
this._computeShowHistoryComponent(entityId)
${this._computeShowHistoryComponent(entityId)
? html`
<mwc-tab-bar
.activeIndex=${this._currTabIndex}
@@ -187,7 +135,7 @@ export class MoreInfoDialog extends LitElement {
>
<mwc-tab
.label=${this.hass.localize(
"ui.dialogs.more_info_control.details"
"ui.dialogs.more_info_control.controls"
)}
></mwc-tab>
<mwc-tab
@@ -212,23 +160,10 @@ export class MoreInfoDialog extends LitElement {
.hass=${this.hass}
></state-card-content>
`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!this._computeShowHistoryComponent(entityId)
? ""
: html`<ha-more-info-history
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-history>
<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-logbook>`}
${this._moreInfoType
? dynamicElement(this._moreInfoType, {
hass: this.hass,
stateObj,
})
: ""}
<more-info-content
.stateObj=${stateObj}
.hass=${this.hass}
></more-info-content>
${stateObj.attributes.restored
? html`
<p>
@@ -253,14 +188,10 @@ export class MoreInfoDialog extends LitElement {
: ""}
`
: html`
<ha-more-info-history
<ha-more-info-tab-history
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-history>
<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-logbook>
></ha-more-info-tab-history>
`
)}
</div>
@@ -268,14 +199,17 @@ export class MoreInfoDialog extends LitElement {
`;
}
protected firstUpdated(): void {
import("./ha-more-info-tab-history");
}
private _enlarge() {
this.large = !this.large;
}
private _computeShowHistoryComponent(entityId) {
return (
(isComponentLoaded(this.hass, "history") ||
isComponentLoaded(this.hass, "logbook")) &&
isComponentLoaded(this.hass, "history") &&
!DOMAINS_MORE_INFO_NO_HISTORY.includes(computeDomain(entityId))
);
}
@@ -340,7 +274,6 @@ export class MoreInfoDialog extends LitElement {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
@media all and (max-width: 450px), all and (max-height: 500px) {

View File

@@ -1,109 +0,0 @@
import {
css,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { throttle } from "../../common/util/throttle";
import "../../components/state-history-charts";
import { getRecentWithCache } from "../../data/cached-history";
import { HistoryResult } from "../../data/history";
import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
@customElement("ha-more-info-history")
export class MoreInfoHistory extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId!: string;
@internalProperty() private _stateHistory?: HistoryResult;
private _throttleGetStateHistory = throttle(() => {
this._getStateHistory();
}, 10000);
protected render(): TemplateResult {
if (!this.entityId) {
return html``;
}
return html`${isComponentLoaded(this.hass, "history")
? html`<state-history-charts
up-to-now
.hass=${this.hass}
.historyData=${this._stateHistory}
.isLoadingData=${!this._stateHistory}
></state-history-charts>`
: ""} `;
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("entityId")) {
this._stateHistory = undefined;
if (!this.entityId) {
return;
}
this._throttleGetStateHistory();
return;
}
if (!this.entityId || !changedProps.has("hass")) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (
oldHass &&
this.hass.states[this.entityId] !== oldHass?.states[this.entityId]
) {
// wait for commit of data (we only account for the default setting of 1 sec)
setTimeout(this._throttleGetStateHistory, 1000);
}
}
private async _getStateHistory(): Promise<void> {
if (!isComponentLoaded(this.hass, "history")) {
return;
}
this._stateHistory = await getRecentWithCache(
this.hass!,
this.entityId,
{
refresh: 60,
cacheKey: `more_info.${this.entityId}`,
hoursToShow: 24,
},
this.hass!.localize,
this.hass!.language
);
}
static get styles() {
return [
haStyle,
css`
state-history-charts {
display: block;
margin-bottom: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-history": MoreInfoHistory;
}
}

View File

@@ -1,171 +0,0 @@
import {
css,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { throttle } from "../../common/util/throttle";
import "../../components/ha-circular-progress";
import "../../components/state-history-charts";
import { getLogbookData, LogbookEntry } from "../../data/logbook";
import "../../panels/logbook/ha-logbook";
import { haStyle, haStyleScrollbar } from "../../resources/styles";
import { HomeAssistant } from "../../types";
@customElement("ha-more-info-logbook")
export class MoreInfoLogbook extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId!: string;
@internalProperty() private _logbookEntries?: LogbookEntry[];
@internalProperty() private _persons = {};
private _lastLogbookDate?: Date;
private _throttleGetLogbookEntries = throttle(() => {
this._getLogBookData();
}, 10000);
protected render(): TemplateResult {
if (!this.entityId) {
return html``;
}
const stateObj = this.hass.states[this.entityId];
if (!stateObj) {
return html``;
}
return html`
${isComponentLoaded(this.hass, "logbook")
? !this._logbookEntries
? html`
<ha-circular-progress
active
alt=${this.hass.localize("ui.common.loading")}
></ha-circular-progress>
`
: this._logbookEntries.length
? html`
<ha-logbook
class="ha-scrollbar"
narrow
no-icon
no-name
.hass=${this.hass}
.entries=${this._logbookEntries}
.userIdToName=${this._persons}
></ha-logbook>
`
: html`<div class="no-entries">
${this.hass.localize("ui.components.logbook.entries_not_found")}
</div>`
: ""}
`;
}
protected firstUpdated(): void {
this._fetchPersonNames();
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("entityId")) {
this._lastLogbookDate = undefined;
this._logbookEntries = undefined;
if (!this.entityId) {
return;
}
this._throttleGetLogbookEntries();
return;
}
if (!this.entityId || !changedProps.has("hass")) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (
oldHass &&
this.hass.states[this.entityId] !== oldHass?.states[this.entityId]
) {
// wait for commit of data (we only account for the default setting of 1 sec)
setTimeout(this._throttleGetLogbookEntries, 1000);
}
}
private async _getLogBookData() {
if (!isComponentLoaded(this.hass, "logbook")) {
return;
}
const lastDate =
this._lastLogbookDate ||
new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
const now = new Date();
const newEntries = await getLogbookData(
this.hass,
lastDate.toISOString(),
now.toISOString(),
this.entityId,
true
);
this._logbookEntries = this._logbookEntries
? [...newEntries, ...this._logbookEntries]
: newEntries;
this._lastLogbookDate = now;
}
private _fetchPersonNames() {
Object.values(this.hass.states).forEach((entity) => {
if (
entity.attributes.user_id &&
computeStateDomain(entity) === "person"
) {
this._persons[entity.attributes.user_id] =
entity.attributes.friendly_name;
}
});
}
static get styles() {
return [
haStyle,
haStyleScrollbar,
css`
.no-entries {
text-align: center;
padding: 16px;
color: var(--secondary-text-color);
}
ha-logbook {
max-height: 250px;
overflow: auto;
display: block;
margin-top: 16px;
}
ha-circular-progress {
display: flex;
justify-content: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-logbook": MoreInfoLogbook;
}
}

View File

@@ -0,0 +1,166 @@
import {
css,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import "../../components/ha-circular-progress";
import "../../components/state-history-charts";
import { getRecentWithCache } from "../../data/cached-history";
import { HistoryResult } from "../../data/history";
import { getLogbookData, LogbookEntry } from "../../data/logbook";
import "../../panels/logbook/ha-logbook";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
@customElement("ha-more-info-tab-history")
export class MoreInfoTabHistoryDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId!: string;
@internalProperty() private _stateHistory?: HistoryResult;
@internalProperty() private _entries?: LogbookEntry[];
@internalProperty() private _persons = {};
private _historyRefreshInterval?: number;
protected render(): TemplateResult {
if (!this.entityId) {
return html``;
}
const stateObj = this.hass.states[this.entityId];
if (!stateObj) {
return html``;
}
return html`
<state-history-charts
up-to-now
.hass=${this.hass}
.historyData=${this._stateHistory}
.isLoadingData=${!this._stateHistory}
></state-history-charts>
${!this._entries
? html`
<ha-circular-progress
active
alt=${this.hass.localize("ui.common.loading")}
></ha-circular-progress>
`
: this._entries.length
? html`
<ha-logbook
narrow
no-icon
no-name
style=${styleMap({
height: `${(this._entries.length + 1) * 56}px`,
})}
.hass=${this.hass}
.entries=${this._entries}
.userIdToName=${this._persons}
></ha-logbook>
`
: ""}
`;
}
protected firstUpdated(): void {
this._fetchPersonNames();
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (!this.entityId) {
clearInterval(this._historyRefreshInterval);
}
if (changedProps.has("entityId")) {
this._stateHistory = undefined;
this._entries = undefined;
this._getStateHistory();
this._getLogBookData();
clearInterval(this._historyRefreshInterval);
this._historyRefreshInterval = window.setInterval(() => {
this._getStateHistory();
}, 60 * 1000);
}
}
private async _getStateHistory(): Promise<void> {
this._stateHistory = await getRecentWithCache(
this.hass!,
this.entityId,
{
refresh: 60,
cacheKey: `more_info.${this.entityId}`,
hoursToShow: 24,
},
this.hass!.localize,
this.hass!.language
);
}
private async _getLogBookData() {
const yesterday = new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
const now = new Date();
this._entries = await getLogbookData(
this.hass,
yesterday.toISOString(),
now.toISOString(),
this.entityId,
true
);
}
private _fetchPersonNames() {
Object.values(this.hass.states).forEach((entity) => {
if (
entity.attributes.user_id &&
computeStateDomain(entity) === "person"
) {
this._persons[entity.attributes.user_id] =
entity.attributes.friendly_name;
}
});
}
static get styles() {
return [
haStyleDialog,
css`
state-history-charts {
display: block;
margin-bottom: 16px;
}
ha-logbook {
max-height: 360px;
}
ha-circular-progress {
display: flex;
justify-content: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-tab-history": MoreInfoTabHistoryDialog;
}
}

View File

@@ -0,0 +1,73 @@
import { HassEntity } from "home-assistant-js-websocket";
import { property, PropertyValues, UpdatingElement } from "lit-element";
import dynamicContentUpdater from "../../common/dom/dynamic_content_updater";
import { stateMoreInfoType } from "../../common/entity/state_more_info_type";
import { HomeAssistant } from "../../types";
import "./controls/more-info-alarm_control_panel";
import "./controls/more-info-automation";
import "./controls/more-info-camera";
import "./controls/more-info-climate";
import "./controls/more-info-configurator";
import "./controls/more-info-counter";
import "./controls/more-info-cover";
import "./controls/more-info-default";
import "./controls/more-info-fan";
import "./controls/more-info-group";
import "./controls/more-info-humidifier";
import "./controls/more-info-input_datetime";
import "./controls/more-info-light";
import "./controls/more-info-lock";
import "./controls/more-info-media_player";
import "./controls/more-info-person";
import "./controls/more-info-script";
import "./controls/more-info-sun";
import "./controls/more-info-timer";
import "./controls/more-info-vacuum";
import "./controls/more-info-water_heater";
import "./controls/more-info-weather";
class MoreInfoContent extends UpdatingElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public stateObj?: HassEntity;
private _detachedChild?: ChildNode;
protected firstUpdated(): void {
this.style.position = "relative";
this.style.display = "block";
}
// This is not a lit element, but an updating element, so we implement update
protected update(changedProps: PropertyValues): void {
super.update(changedProps);
const stateObj = this.stateObj;
const hass = this.hass;
if (!stateObj || !hass) {
if (this.lastChild) {
this._detachedChild = this.lastChild;
// Detach child to prevent it from doing work.
this.removeChild(this.lastChild);
}
return;
}
if (this._detachedChild) {
this.appendChild(this._detachedChild);
this._detachedChild = undefined;
}
const moreInfoType =
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
? stateObj.attributes.custom_ui_more_info
: "more-info-" + stateMoreInfoType(stateObj);
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
hass,
stateObj,
});
}
}
customElements.define("more-info-content", MoreInfoContent);

View File

@@ -7,3 +7,5 @@ import "../util/legacy-support";
setPassiveTouchGestures(true);
(window as any).frontendVersion = __VERSION__;
import("../resources/html-import/polyfill");

View File

@@ -48,7 +48,7 @@
}
@media (prefers-color-scheme: dark) {
html {
background-color: #111111;
background-color: var(--primary-background-color, #111111);
}
#ha-init-skeleton::before {
background-color: #1c1c1c;
@@ -100,5 +100,9 @@
{% endfor -%}
}
</script>
{% for extra_url in extra_urls -%}
<link rel="import" href="{{ extra_url }}" async />
{% endfor -%}
</body>
</html>

View File

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

View File

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

View File

@@ -3,26 +3,26 @@ import {
css,
CSSResult,
customElement,
eventOptions,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
eventOptions,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { restoreScroll } from "../common/decorators/restore-scroll";
import { navigate } from "../common/navigate";
import { computeRTL } from "../common/util/compute_rtl";
import "../components/ha-icon";
import "../components/ha-icon-button-arrow-prev";
import "../components/ha-menu-button";
import "../components/ha-svg-icon";
import "../components/ha-tab";
import "../components/ha-icon-button-arrow-prev";
import { HomeAssistant, Route } from "../types";
import "../components/ha-svg-icon";
import "../components/ha-icon";
import "../components/ha-tab";
import { restoreScroll } from "../common/decorators/restore-scroll";
import { computeRTL } from "../common/util/compute_rtl";
export interface PageNavigation {
path: string;
@@ -132,7 +132,7 @@ class HassTabsSubpage extends LitElement {
this.hass.language,
this.narrow
);
const showTabs = tabs.length > 1 || !this.narrow;
return html`
<div class="toolbar">
${this.mainPage
@@ -152,7 +152,7 @@ class HassTabsSubpage extends LitElement {
${this.narrow
? html` <div class="main-title"><slot name="header"></slot></div> `
: ""}
${showTabs
${tabs.length > 1 || !this.narrow
? html`
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
${tabs}
@@ -163,15 +163,10 @@ class HassTabsSubpage extends LitElement {
<slot name="toolbar-icon"></slot>
</div>
</div>
<div
class="content ${classMap({ tabs: showTabs })}"
@scroll=${this._saveScrollPos}
>
<div class="content" @scroll=${this._saveScrollPos}>
<slot></slot>
</div>
<div id="fab" class="${classMap({ tabs: showTabs })}">
<slot name="fab"></slot>
</div>
<div id="fab"><slot name="fab"></slot></div>
`;
}
@@ -279,13 +274,12 @@ class HassTabsSubpage extends LitElement {
margin-left: env(safe-area-inset-left);
margin-right: env(safe-area-inset-right);
height: calc(100% - 65px);
height: calc(100% - 65px - env(safe-area-inset-bottom));
overflow-y: auto;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
:host([narrow]) .content.tabs {
:host([narrow]) .content {
height: calc(100% - 128px);
height: calc(100% - 128px - env(safe-area-inset-bottom));
}
@@ -296,7 +290,7 @@ class HassTabsSubpage extends LitElement {
bottom: calc(16px + env(safe-area-inset-bottom));
z-index: 1;
}
:host([narrow]) #fab.tabs {
:host([narrow]) #fab {
bottom: calc(84px + env(safe-area-inset-bottom));
}
#fab[is-wide] {

View File

@@ -24,7 +24,6 @@ const NON_SWIPABLE_PANELS = ["map"];
declare global {
// for fire event
interface HASSDomEvents {
"hass-open-menu": undefined;
"hass-toggle-menu": undefined;
"hass-show-notifications": undefined;
}
@@ -93,17 +92,6 @@ class HomeAssistantMain extends LitElement {
protected firstUpdated() {
import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar");
this.addEventListener("hass-open-menu", () => {
if (this._sidebarNarrow) {
this.drawer.open();
} else {
fireEvent(this, "hass-dock-sidebar", {
dock: "docked",
});
setTimeout(() => this.appLayout.resetLayout());
}
});
this.addEventListener("hass-toggle-menu", () => {
if (this._sidebarNarrow) {
if (this.drawer.opened) {

View File

@@ -6,9 +6,9 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,25 +1,24 @@
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "../../../../components/ha-icon-button";
import "@polymer/paper-item/paper-item";
import "@material/mwc-list/mwc-list-item";
import "../../../../components/ha-button-menu";
import { mdiDotsVertical } from "@mdi/js";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
} from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
import "../../../../components/ha-icon-button";
import { Condition } from "../../../../data/automation";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../../types";
import "./ha-automation-condition-editor";
import { haStyle } from "../../../../resources/styles";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
export interface ConditionElement extends LitElement {
condition: Condition;
@@ -82,12 +81,12 @@ export default class HaAutomationConditionRow extends LitElement {
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item>
<mwc-list-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item class="warning">
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
@@ -110,7 +109,6 @@ export default class HaAutomationConditionRow extends LitElement {
this._switchYamlMode();
break;
case 1:
fireEvent(this, "duplicate");
break;
case 2:
this._onDelete();
@@ -135,23 +133,20 @@ export default class HaAutomationConditionRow extends LitElement {
this._yamlMode = !this._yamlMode;
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.card-menu {
float: right;
z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
}
.rtl .card-menu {
float: left;
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
`,
];
static get styles(): CSSResult {
return css`
.card-menu {
float: right;
z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
}
.rtl .card-menu {
float: left;
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
`;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,6 @@ import {
showAutomationEditor,
triggerAutomation,
} from "../../../data/automation";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import "../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
@@ -36,9 +35,9 @@ import { showThingtalkDialog } from "./show-dialog-thingtalk";
class HaAutomationPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide!: boolean;
@property() public isWide!: boolean;
@property({ type: Boolean }) public narrow!: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@@ -59,7 +58,7 @@ class HaAutomationPicker extends LitElement {
toggle: {
title: "",
type: "icon",
template: (_toggle, automation: any) =>
template: (_toggle, automation) =>
html`
<ha-entity-toggle
.hass=${this.hass}
@@ -92,11 +91,10 @@ class HaAutomationPicker extends LitElement {
if (!narrow) {
columns.execute = {
title: "",
template: (_info, automation: any) => html`
template: (_info, automation) => html`
<mwc-button
.automation=${automation}
@click=${(ev) => this._execute(ev)}
.disabled=${UNAVAILABLE_STATES.includes(automation.state)}
>
${this.hass.localize("ui.card.automation.trigger")}
</mwc-button>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -103,9 +103,7 @@ class CloudAlexa extends LitElement {
this._entities.forEach((entity) => {
const stateObj = this.hass.states[entity.entity_id];
const config = this._entityConfigs[entity.entity_id] || {
should_expose: null,
};
const config = this._entityConfigs[entity.entity_id] || {};
const isExposed = emptyFilter
? this._configIsExposed(entity.entity_id, config)
: filterFunc(entity.entity_id);
@@ -321,7 +319,9 @@ class CloudAlexa extends LitElement {
}
private _configIsExposed(entityId: string, config: AlexaEntityConfig) {
return config.should_expose ?? this._configIsDomainExposed(entityId);
return config.should_expose === null
? this._configIsDomainExposed(entityId)
: config.should_expose;
}
private async _exposeChanged(ev: CustomEvent<ActionDetail>) {

View File

@@ -109,9 +109,7 @@ class CloudGoogleAssistant extends LitElement {
this._entities.forEach((entity) => {
const stateObj = this.hass.states[entity.entity_id];
const config = this._entityConfigs[entity.entity_id] || {
should_expose: null,
};
const config = this._entityConfigs[entity.entity_id] || {};
const isExposed = emptyFilter
? this._configIsExposed(entity.entity_id, config)
: filterFunc(entity.entity_id);
@@ -326,7 +324,9 @@ class CloudGoogleAssistant extends LitElement {
}
private _configIsExposed(entityId: string, config: GoogleEntityConfig) {
return config.should_expose ?? this._configIsDomainExposed(entityId);
return config.should_expose === null
? this._configIsDomainExposed(entityId)
: config.should_expose;
}
private async _fetchData() {

View File

@@ -58,8 +58,6 @@ import {
loadEntityEditorDialog,
showEntityEditorDialog,
} from "./show-dialog-entity-editor";
import { haStyle } from "../../../resources/styles";
import { UNAVAILABLE } from "../../../data/entity";
export interface StateEntity extends EntityRegistryEntry {
readonly?: boolean;
@@ -282,7 +280,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
for (const entry of entities) {
const entity = this.hass.states[entry.entity_id];
const unavailable = entity?.state === UNAVAILABLE;
const unavailable = entity?.state === "unavailable";
const restored = entity?.attributes.restored;
if (!showUnavailable && unavailable) {
@@ -380,7 +378,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
"ui.panel.config.entities.picker.disable_selected.button"
)}</mwc-button
>
<mwc-button @click=${this._removeSelected} class="warning"
<mwc-button @click=${this._removeSelected}
>${this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.button"
)}</mwc-button
@@ -408,7 +406,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
)}
</paper-tooltip>
<ha-icon-button
class="warning"
id="remove-btn"
icon="hass:delete"
@click=${this._removeSelected}
@@ -724,114 +721,111 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
navigate(this, window.location.pathname, true);
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
a {
color: var(--primary-color);
}
h2 {
margin-top: 0;
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
p {
font-family: var(--paper-font-subhead_-_font-family);
-webkit-font-smoothing: var(
--paper-font-subhead_-_-webkit-font-smoothing
);
font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height);
}
ha-data-table {
width: 100%;
--data-table-border-width: 0;
}
:host(:not([narrow])) ha-data-table {
height: calc(100vh - 65px);
display: block;
}
ha-button-menu {
margin-right: 8px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
}
search-input {
margin-left: 16px;
flex-grow: 1;
position: relative;
top: 2px;
}
.search-toolbar search-input {
margin-left: 8px;
top: 1px;
}
.search-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--secondary-text-color);
}
.search-toolbar ha-button-menu {
position: static;
}
.selected-txt {
font-weight: bold;
padding-left: 16px;
}
.table-header .selected-txt {
margin-top: 20px;
}
.search-toolbar .selected-txt {
font-size: 16px;
}
.header-btns > mwc-button,
.header-btns > ha-icon-button {
margin: 8px;
}
.active-filters {
color: var(--primary-text-color);
position: relative;
display: flex;
align-items: center;
padding: 2px 2px 2px 8px;
margin-left: 4px;
font-size: 14px;
}
.active-filters ha-icon {
color: var(--primary-color);
}
.active-filters mwc-button {
margin-left: 8px;
}
.active-filters::before {
background-color: var(--primary-color);
opacity: 0.12;
border-radius: 4px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: "";
}
`,
];
static get styles(): CSSResult {
return css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
a {
color: var(--primary-color);
}
h2 {
margin-top: 0;
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
p {
font-family: var(--paper-font-subhead_-_font-family);
-webkit-font-smoothing: var(
--paper-font-subhead_-_-webkit-font-smoothing
);
font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height);
}
ha-data-table {
width: 100%;
--data-table-border-width: 0;
}
:host(:not([narrow])) ha-data-table {
height: calc(100vh - 65px);
display: block;
}
ha-button-menu {
margin-right: 8px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
}
search-input {
margin-left: 16px;
flex-grow: 1;
position: relative;
top: 2px;
}
.search-toolbar search-input {
margin-left: 8px;
top: 1px;
}
.search-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--secondary-text-color);
}
.search-toolbar ha-button-menu {
position: static;
}
.selected-txt {
font-weight: bold;
padding-left: 16px;
}
.table-header .selected-txt {
margin-top: 20px;
}
.search-toolbar .selected-txt {
font-size: 16px;
}
.header-btns > mwc-button,
.header-btns > ha-icon-button {
margin: 8px;
}
.active-filters {
color: var(--primary-text-color);
position: relative;
display: flex;
align-items: center;
padding: 2px 2px 2px 8px;
margin-left: 4px;
font-size: 14px;
}
.active-filters ha-icon {
color: var(--primary-color);
}
.active-filters mwc-button {
margin-left: 8px;
}
.active-filters::before {
background-color: var(--primary-color);
opacity: 0.12;
border-radius: 4px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: "";
}
`;
}
}

View File

@@ -6,9 +6,9 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event";
@@ -48,12 +48,12 @@ class HaInputNumberForm extends LitElement {
this._max = item.max ?? 100;
this._min = item.min ?? 0;
this._mode = item.mode || "slider";
this._step = item.step ?? 1;
this._step = item.step || 1;
this._unit_of_measurement = item.unit_of_measurement;
} else {
this._item = {
min: 0,
max: 100,
max: 0,
};
this._name = "";
this._icon = "";
@@ -176,10 +176,8 @@ class HaInputNumberForm extends LitElement {
return;
}
ev.stopPropagation();
const target = ev.target as any;
const configValue = target.configValue;
const value =
target.type === "number" ? Number(ev.detail.value) : ev.detail.value;
const configValue = (ev.target as any).configValue;
const value = ev.detail.value;
if (this[`_${configValue}`] === value) {
return;
}

View File

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

View File

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

View File

@@ -11,9 +11,9 @@ import {
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/buttons/ha-call-service-button";
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
import {
DataTableColumnContainer,
RowClickedEvent,
@@ -117,6 +117,7 @@ class OZWNetworkNodes extends LitElement {
.data=${this._nodes}
id="node_id"
@row-click=${this._handleRowClicked}
back-path="/config/ozw/network/${this.ozwInstance}/dashboard"
>
</hass-tabs-subpage-data-table>
`;

View File

@@ -1,21 +1,14 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiContentCopy } from "@mdi/js";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-tooltip/paper-tooltip";
import type { PaperTooltipElement } from "@polymer/paper-tooltip/paper-tooltip";
import {
css,
CSSResult,
html,
internalProperty,
LitElement,
property,
query,
internalProperty,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/dialog/ha-paper-dialog";
import "../../../components/ha-svg-icon";
import {
domainToName,
fetchIntegrationManifest,
@@ -23,11 +16,12 @@ import {
IntegrationManifest,
} from "../../../data/integration";
import { getLoggedErrorIntegration } from "../../../data/system_log";
import type { PolymerChangedEvent } from "../../../polymer-types";
import { PolymerChangedEvent } from "../../../polymer-types";
import { haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import type { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
import { HomeAssistant } from "../../../types";
import { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
import { formatSystemLogTime } from "./util";
import { fireEvent } from "../../../common/dom/fire_event";
class DialogSystemLogDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -36,8 +30,6 @@ class DialogSystemLogDetail extends LitElement {
@internalProperty() private _manifest?: IntegrationManifest;
@query("paper-tooltip") private _toolTip?: PaperTooltipElement;
public async showDialog(params: SystemLogDetailDialogParams): Promise<void> {
this._params = params;
this._manifest = undefined;
@@ -74,25 +66,13 @@ class DialogSystemLogDetail extends LitElement {
opened
@opened-changed="${this._openedChanged}"
>
<div class="heading">
<h2>
${this.hass.localize(
"ui.panel.config.logs.details",
"level",
item.level
)}
</h2>
<mwc-icon-button id="copy" @click=${this._copyLog}>
<ha-svg-icon .path=${mdiContentCopy}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip
manual-mode
for="copy"
position="top"
animation-delay="0"
>${this.hass.localize("ui.common.copied")}</paper-tooltip
>
</div>
<h2>
${this.hass.localize(
"ui.panel.config.logs.details",
"level",
item.level
)}
</h2>
<paper-dialog-scrollable>
<p>
Logger: ${item.name}<br />
@@ -168,25 +148,6 @@ class DialogSystemLogDetail extends LitElement {
}
}
private _copyLog(): void {
const copyElement = this.shadowRoot?.querySelector(
"paper-dialog-scrollable"
) as HTMLElement;
const selection = window.getSelection()!;
const range = document.createRange();
range.selectNodeContents(copyElement);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand("copy");
window.getSelection()!.removeAllRanges();
this._toolTip!.show();
setTimeout(() => this._toolTip?.hide(), 3000);
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
@@ -203,15 +164,6 @@ class DialogSystemLogDetail extends LitElement {
pre {
margin-bottom: 0;
}
.heading {
display: flex;
display: flex;
align-items: center;
justify-content: space-between;
}
.heading ha-svg-icon {
cursor: pointer;
}
`,
];
}

View File

@@ -198,7 +198,6 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
? ""
: html`
<ha-icon-button
class="warning"
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.scene.picker.delete_scene"

View File

@@ -18,14 +18,12 @@ import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-card";
import "../../../components/ha-icon-input";
import { showToast } from "../../../util/toast";
import "@material/mwc-fab";
import {
Action,
deleteScript,
getScriptEditorInitData,
ScriptConfig,
triggerScript,
MODES,
MODES_MAX,
} from "../../../data/script";
@@ -76,7 +74,6 @@ export class HaScriptEditor extends LitElement {
? ""
: html`
<ha-icon-button
class="warning"
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.script.editor.delete_script"
@@ -195,22 +192,6 @@ export class HaScriptEditor extends LitElement {
</paper-input>`
: html``}
</div>
${this.scriptEntityId
? html`
<div class="card-actions layout horizontal justified center">
<span></span>
<mwc-button
@click=${this._runScript}
title="${this.hass.localize(
"ui.panel.config.script.picker.activate_script"
)}"
?disabled=${this._dirty}
>
${this.hass.localize("ui.card.script.execute")}
</mwc-button>
</div>
`
: ``}
</ha-card>
</ha-config-section>
@@ -319,18 +300,6 @@ export class HaScriptEditor extends LitElement {
}
}
private async _runScript(ev) {
ev.stopPropagation();
await triggerScript(this.hass, this.scriptEntityId);
showToast(this, {
message: this.hass.localize(
"ui.notification_toast.triggered",
"name",
this._config!.alias
),
});
}
private _modeChanged(ev: CustomEvent) {
const mode = ((ev.target as PaperListboxElement)?.selectedItem as any)
?.mode;

View File

@@ -12,7 +12,7 @@ import {
property,
TemplateResult,
} from "lit-element";
import { componentsWithService } from "../../../common/config/components_with_service";
import { isServiceLoaded } from "../../../common/config/is_service_loaded";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-card";
import { checkCoreConfig } from "../../../data/core";
@@ -49,10 +49,11 @@ export class HaConfigServerControl extends LitElement {
changedProperties.has("hass") &&
(!oldHass || oldHass.config.components !== this.hass.config.components)
) {
this._reloadableDomains = componentsWithService(
this.hass,
"reload"
).sort();
this._reloadableDomains = this.hass.config.components.filter(
(component) =>
!component.includes(".") &&
isServiceLoaded(this.hass, component, "reload")
);
}
}
@@ -202,23 +203,24 @@ export class HaConfigServerControl extends LitElement {
)}
</ha-call-service-button>
</div>
${this._reloadableDomains.map(
(domain) =>
html`<div class="card-actions">
<ha-call-service-button
.hass=${this.hass}
.domain=${domain}
service="reload"
>${this.hass.localize(
`ui.panel.config.server_control.section.reloading.${domain}`
) ||
this.hass.localize(
"ui.panel.config.server_control.section.reloading.reload",
"domain",
domainToName(this.hass.localize, domain)
)}
</ha-call-service-button>
</div>`
${this._reloadableDomains.map((domain) =>
isServiceLoaded(this.hass, domain, "reload")
? html`<div class="card-actions">
<ha-call-service-button
.hass=${this.hass}
.domain=${domain}
service="reload"
>${this.hass.localize(
`ui.panel.config.server_control.section.reloading.${domain}`
) ||
this.hass.localize(
"ui.panel.config.server_control.section.reloading.reload",
"domain",
domainToName(this.hass.localize, domain)
)}
</ha-call-service-button>
</div>`
: ""
)}
</ha-card>
`

View File

@@ -1,5 +1,3 @@
import "@material/mwc-fab";
import { mdiPlus } from "@mdi/js";
import {
customElement,
LitElement,
@@ -13,7 +11,7 @@ import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/ha-svg-icon";
import "@material/mwc-fab";
import { deleteUser, fetchUsers, updateUser, User } from "../../../data/user";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-tabs-subpage-data-table";
@@ -21,6 +19,8 @@ import { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
import { showAddUserDialog } from "./show-dialog-add-user";
import { showUserDetailDialog } from "./show-dialog-user-detail";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-config-users")
export class HaConfigUsers extends LitElement {
@@ -56,7 +56,7 @@ export class HaConfigUsers extends LitElement {
),
sortable: true,
filterable: true,
width: "30%",
width: "25%",
template: (groupIds) => html`
${this.hass.localize(`groups.${groupIds[0]}`)}
`,
@@ -66,7 +66,6 @@ export class HaConfigUsers extends LitElement {
"ui.panel.config.users.picker.headers.system"
),
type: "icon",
width: "80px",
sortable: true,
filterable: true,
template: (generated) => html`

View File

@@ -13,10 +13,7 @@ import { classMap } from "lit-html/directives/class-map";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-circular-progress";
import "../../../components/ha-code-editor";
import {
RenderTemplateResult,
subscribeRenderTemplate,
} from "../../../data/ws-templates";
import { subscribeRenderTemplate } from "../../../data/ws-templates";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
@@ -34,9 +31,10 @@ The temperature is {{ my_test_json.temperature }} {{ my_test_json.unit }}.
The sun will rise at {{ as_timestamp(strptime(state_attr("sun.sun", "next_rising"), "")) | timestamp_local }}.
{%- endif %}
For loop example getting entity values in the weather domain:
For loop example getting 3 entity values:
{% for state in states.weather -%}
{% for states in states | slice(3) -%}
{% set state = states | first %}
{%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}
{{ state.name | lower }} is {{state.state_with_unit}}
{%- endfor %}.`;
@@ -47,11 +45,11 @@ class HaPanelDevTemplate extends LitElement {
@property() public narrow!: boolean;
@internalProperty() private _error?: string;
@internalProperty() private _error = false;
@internalProperty() private _rendering = false;
@internalProperty() private _templateResult?: RenderTemplateResult;
@internalProperty() private _processed = "";
@internalProperty() private _unsubRenderTemplate?: Promise<UnsubscribeFunc>;
@@ -142,65 +140,9 @@ class HaPanelDevTemplate extends LitElement {
.active=${this._rendering}
size="small"
></ha-circular-progress>
<pre
class="rendered ${classMap({ error: Boolean(this._error) })}"
><!-- display: block -->${this._error}${this._templateResult
?.result}</pre>
${!this._templateResult?.listeners
? ""
: this._templateResult.listeners.all
? html`
<h3 class="all_listeners">
${this.hass.localize(
"ui.panel.developer-tools.tabs.templates.all_listeners"
)}
</h3>
`
: this._templateResult.listeners.domains.length ||
this._templateResult.listeners.entities.length
? html`
<h3>
${this.hass.localize(
"ui.panel.developer-tools.tabs.templates.listeners"
)}
</h3>
<ul>
${this._templateResult.listeners.domains
.sort()
.map(
(domain) =>
html`
<li>
<b
>${this.hass.localize(
"ui.panel.developer-tools.tabs.templates.domain"
)}</b
>: ${domain}
</li>
`
)}
${this._templateResult.listeners.entities
.sort()
.map(
(entity_id) =>
html`
<li>
<b
>${this.hass.localize(
"ui.panel.developer-tools.tabs.templates.entity"
)}</b
>: ${entity_id}
</li>
`
)}
</ul>
`
: html` <span class="all_listeners">
${this.hass.localize(
"ui.panel.developer-tools.tabs.templates.no_listeners"
)}
</span>`}
<pre class="rendered ${classMap({ error: this._error })}">
${this._processed}</pre
>
</div>
</div>
`;
@@ -248,12 +190,6 @@ class HaPanelDevTemplate extends LitElement {
@apply --paper-font-code1;
clear: both;
white-space: pre-wrap;
background-color: var(--secondary-background-color);
padding: 8px;
}
.all_listeners {
color: var(--warning-color);
}
.rendered.error {
@@ -275,7 +211,7 @@ class HaPanelDevTemplate extends LitElement {
private _templateChanged(ev) {
this._template = ev.detail.value;
if (this._error) {
this._error = undefined;
this._error = false;
}
this._debounceRender();
}
@@ -287,8 +223,7 @@ class HaPanelDevTemplate extends LitElement {
this._unsubRenderTemplate = subscribeRenderTemplate(
this.hass.connection,
(result) => {
this._templateResult = result;
this._error = undefined;
this._processed = result;
},
{
template: this._template,
@@ -296,10 +231,9 @@ class HaPanelDevTemplate extends LitElement {
);
await this._unsubRenderTemplate;
} catch (err) {
this._error = "Unknown error";
this._error = true;
if (err.message) {
this._error = err.message;
this._templateResult = undefined;
this._processed = err.message;
}
this._unsubRenderTemplate = undefined;
} finally {

View File

@@ -79,12 +79,10 @@ class HaPanelHistory extends LitElement {
></ha-date-range-picker>
</div>
${this._isLoading
? html`<div class="progress-wrapper">
<ha-circular-progress
active
alt=${this.hass.localize("ui.common.loading")}
></ha-circular-progress>
</div>`
? html`<ha-circular-progress
active
alt=${this.hass.localize("ui.common.loading")}
></ha-circular-progress>`
: html`
<state-history-charts
.hass=${this.hass}
@@ -198,19 +196,6 @@ class HaPanelHistory extends LitElement {
.content {
padding: 0 16px 16px;
}
.progress-wrapper {
height: calc(100vh - 136px);
}
:host([narrow]) .progress-wrapper {
height: calc(100vh - 198px);
}
.progress-wrapper {
position: relative;
}
ha-circular-progress {
position: absolute;
left: 50%;

View File

@@ -21,6 +21,7 @@ import { computeRTL, emitRTLDirection } from "../../common/util/compute_rtl";
import "../../components/ha-circular-progress";
import "../../components/ha-icon";
import { LogbookEntry } from "../../data/logbook";
import { haStyleScrollbar } from "../../resources/styles";
import { HomeAssistant } from "../../types";
@customElement("ha-logbook")
@@ -37,9 +38,6 @@ class HaLogbook extends LitElement {
@property({ attribute: "rtl", type: Boolean })
private _rtl = false;
@property({ type: Boolean, attribute: "virtualize", reflect: true })
public virtualize = false;
@property({ type: Boolean, attribute: "no-icon" })
public noIcon = false;
@@ -69,14 +67,14 @@ class HaLogbook extends LitElement {
if (!this.entries?.length) {
return html`
<div class="container no-entries" .dir=${emitRTLDirection(this._rtl)}>
${this.hass.localize("ui.components.logbook.entries_not_found")}
${this.hass.localize("ui.panel.logbook.entries_not_found")}
</div>
`;
}
return html`
<div
class="container ${classMap({
class="container ha-scrollbar ${classMap({
narrow: this.narrow,
rtl: this._rtl,
"no-name": this.noName,
@@ -84,15 +82,11 @@ class HaLogbook extends LitElement {
})}"
@scroll=${this._saveScrollPos}
>
${this.virtualize
? scroll({
items: this.entries,
renderItem: (item: LogbookEntry, index?: number) =>
this._renderLogbookItem(item, index),
})
: this.entries.map((item, index) =>
this._renderLogbookItem(item, index)
)}
${scroll({
items: this.entries,
renderItem: (item: LogbookEntry, index?: number) =>
this._renderLogbookItem(item, index),
})}
</div>
`;
}
@@ -149,23 +143,20 @@ class HaLogbook extends LitElement {
>
`
: ""}
${item.message}
${item_username
? ` by ${item_username}`
: !item.context_event_type
<span class="item-message">${item.message}</span>
<span>${item_username ? ` (${item_username})` : ``}</span>
${!item.context_event_type
? ""
: item.context_event_type === "call_service"
? // Service Call
` by service
html` by service
${item.context_domain}.${item.context_service}`
: item.context_entity_id === item.entity_id
? // HomeKit or something that self references
` by
${
item.context_name
? item.context_name
: item.context_event_type
}`
html` by
${item.context_name
? item.context_name
: item.context_event_type}`
: // Another entity such as an automation or script
html` by
<a
@@ -194,104 +185,106 @@ class HaLogbook extends LitElement {
});
}
static get styles(): CSSResult {
return css`
:host {
display: block;
height: 100%;
}
static get styles(): CSSResult[] {
return [
haStyleScrollbar,
css`
:host {
display: block;
height: 100%;
}
.rtl {
direction: ltr;
}
.rtl {
direction: ltr;
}
.entry-container {
width: 100%;
}
.entry-container {
width: 100%;
}
.entry {
display: flex;
width: 100%;
line-height: 2em;
padding: 8px 16px;
box-sizing: border-box;
border-top: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
.entry {
display: flex;
width: 100%;
line-height: 2em;
padding: 8px 16px;
box-sizing: border-box;
border-top: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
.time {
display: flex;
justify-content: center;
flex-direction: column;
width: 75px;
flex-shrink: 0;
font-size: 12px;
color: var(--secondary-text-color);
}
.time {
display: flex;
justify-content: center;
flex-direction: column;
width: 65px;
flex-shrink: 0;
font-size: 12px;
color: var(--secondary-text-color);
}
.date {
margin: 8px 0;
padding: 0 16px;
}
.date {
margin: 8px 0;
padding: 0 16px;
}
.narrow .date {
padding: 0 8px;
}
.narrow .date {
padding: 0 8px;
}
.rtl .date {
direction: rtl;
}
.rtl .date {
direction: rtl;
}
.icon-message {
display: flex;
align-items: center;
}
.icon-message {
display: flex;
align-items: center;
}
.no-entries {
text-align: center;
color: var(--secondary-text-color);
}
.no-entries {
text-align: center;
}
ha-icon {
margin: 0 8px 0 16px;
flex-shrink: 0;
color: var(--primary-text-color);
}
ha-icon {
margin: 0 8px 0 16px;
flex-shrink: 0;
color: var(--primary-text-color);
}
.message {
color: var(--primary-text-color);
}
.message {
color: var(--primary-text-color);
}
.no-name .message:first-letter {
text-transform: capitalize;
}
.no-name .item-message {
text-transform: capitalize;
}
a {
color: var(--primary-color);
}
a {
color: var(--primary-color);
}
.uni-virtualizer-host {
display: block;
position: relative;
contain: strict;
height: 100%;
overflow: auto;
}
.uni-virtualizer-host {
display: block;
position: relative;
contain: strict;
height: 100%;
overflow: auto;
}
.uni-virtualizer-host > * {
box-sizing: border-box;
}
.uni-virtualizer-host > * {
box-sizing: border-box;
}
.narrow .entry {
flex-direction: column;
line-height: 1.5;
padding: 8px;
}
.narrow .entry {
flex-direction: column;
line-height: 1.5;
padding: 8px;
}
.narrow .icon-message ha-icon {
margin-left: 0;
}
`;
.narrow .icon-message ha-icon {
margin-left: 0;
}
`,
];
}
}

View File

@@ -1,33 +1,33 @@
import { mdiRefresh } from "@mdi/js";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import {
css,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
} from "lit-element";
import "../../components/ha-icon-button";
import "../../components/ha-circular-progress";
import { computeRTL } from "../../common/util/compute_rtl";
import "../../components/entity/ha-entity-picker";
import "../../components/ha-circular-progress";
import "../../components/ha-date-range-picker";
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
import "../../components/ha-icon-button";
import "../../components/ha-menu-button";
import "../../layouts/ha-app-layout";
import "./ha-logbook";
import {
LitElement,
property,
internalProperty,
customElement,
html,
css,
PropertyValues,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { haStyle } from "../../resources/styles";
import { fetchUsers } from "../../data/user";
import { fetchPersons } from "../../data/person";
import {
clearLogbookCache,
getLogbookData,
LogbookEntry,
} from "../../data/logbook";
import { fetchPersons } from "../../data/person";
import { fetchUsers } from "../../data/user";
import "../../layouts/ha-app-layout";
import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import "./ha-logbook";
import { mdiRefresh } from "@mdi/js";
import "../../components/ha-date-range-picker";
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
@customElement("ha-panel-logbook")
export class HaPanelLogbook extends LitElement {
@@ -125,7 +125,6 @@ export class HaPanelLogbook extends LitElement {
.hass=${this.hass}
.entries=${this._entries}
.userIdToName=${this._userIdToName}
virtualize
></ha-logbook>`}
</ha-app-layout>
`;

View File

@@ -21,7 +21,6 @@ import { DOMAINS_TOGGLE } from "../../../common/const";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { computeActiveState } from "../../../common/entity/compute_active_state";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { stateIcon } from "../../../common/entity/state_icon";
@@ -37,6 +36,7 @@ import { hasAction } from "../common/has-action";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { ButtonCardConfig } from "./types";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
@customElement("hui-button-card")
export class HuiButtonCard extends LitElement implements LovelaceCard {
@@ -63,6 +63,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
return {
type: "button",
tap_action: { action: "toggle" },
hold_action: { action: "more-info" },
show_icon: true,
show_name: true,
show_state: false,
entity: foundEntities[0] || "",
};
}
@@ -87,18 +92,29 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
}
this._config = {
tap_action: {
action:
config.entity && DOMAINS_TOGGLE.has(computeDomain(config.entity))
? "toggle"
: "more-info",
},
hold_action: { action: "more-info" },
double_tap_action: { action: "none" },
show_icon: true,
show_name: true,
state_color: true,
...config,
};
if (config.entity && DOMAINS_TOGGLE.has(computeDomain(config.entity))) {
this._config = {
tap_action: {
action: "toggle",
},
...this._config,
};
} else {
this._config = {
tap_action: {
action: "more-info",
},
...this._config,
};
}
}
protected shouldUpdate(changedProps: PropertyValues): boolean {

View File

@@ -76,11 +76,11 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
private _resizeObserver?: ResizeObserver;
public setConfig(config: CalendarCardConfig): void {
if (!config.entities?.length) {
if (!config.entities) {
throw new Error("Entities must be defined");
}
if (!Array.isArray(config.entities)) {
if (config.entities && !Array.isArray(config.entities)) {
throw new Error("Entities need to be an array");
}

View File

@@ -50,7 +50,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
["light", "switch", "sensor"]
);
return { type: "entities", entities: foundEntities };
return { type: "entities", title: "My Title", entities: foundEntities };
}
@internalProperty() private _config?: EntitiesCardConfig;

View File

@@ -43,8 +43,8 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
return css`
:host {
display: block;
background-color: var(--error-color);
color: var(--color-on-error, white);
background-color: #ef5350;
color: white;
padding: 8px;
font-weight: 500;
user-select: text;

View File

@@ -3,9 +3,9 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -20,22 +20,18 @@ import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import {
ActionHandlerEvent,
CallServiceActionConfig,
MoreInfoActionConfig,
} from "../../../data/lovelace";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { findEntities } from "../common/find-entites";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
import { processConfigEntities } from "../common/process-config-entities";
import "../components/hui-timestamp-display";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import "../components/hui-warning-element";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import "../components/hui-timestamp-display";
import { GlanceCardConfig, GlanceConfigEntity } from "./types";
import { createEntityNotFoundWarning } from "../components/hui-warning";
@customElement("hui-glance-card")
export class HuiGlanceCard extends LitElement implements LovelaceCard {
@@ -90,14 +86,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
state_color: true,
...config,
};
const entities = processConfigEntities<GlanceConfigEntity>(
config.entities
).map((entityConf) => {
return {
hold_action: { action: "more-info" } as MoreInfoActionConfig,
...entityConf,
};
});
const entities = processConfigEntities<GlanceConfigEntity>(config.entities);
for (const entity of entities) {
if (
@@ -106,7 +95,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
!entity.tap_action.service) ||
(entity.hold_action &&
entity.hold_action.action === "call-service" &&
!(entity.hold_action as CallServiceActionConfig).service)
!entity.hold_action.service)
) {
throw new Error(
'Missing required property "service" when tap_action or hold_action is call-service'

Some files were not shown because too many files have changed in this diff Show More