mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-17 08:59:43 +00:00
Compare commits
93 Commits
20200907.0
...
script-yam
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bda951e6d1 | ||
![]() |
92ed14c0e4 | ||
![]() |
5b94a4de9a | ||
![]() |
709112c498 | ||
![]() |
e465ec8835 | ||
![]() |
f6eb31bf9d | ||
![]() |
426f939982 | ||
![]() |
fab6cebf0d | ||
![]() |
ff081dd0f1 | ||
![]() |
868399ed6f | ||
![]() |
1bc9b95289 | ||
![]() |
9af805ab5e | ||
![]() |
6b88081360 | ||
![]() |
50d37ce4f6 | ||
![]() |
af0246cd27 | ||
![]() |
857e4e49d8 | ||
![]() |
c1afed7f98 | ||
![]() |
5480e54185 | ||
![]() |
99d0a0a6fd | ||
![]() |
8a998369d6 | ||
![]() |
8b490c5047 | ||
![]() |
7e70ba6ab2 | ||
![]() |
90e09fc384 | ||
![]() |
266f2e763d | ||
![]() |
c979cfb912 | ||
![]() |
8ee29b1e43 | ||
![]() |
26fbc07cac | ||
![]() |
f01fe65be4 | ||
![]() |
3fdd6a80f9 | ||
![]() |
da1de8db1d | ||
![]() |
dd1bf7b49d | ||
![]() |
f18913b5a0 | ||
![]() |
a2cd227f1a | ||
![]() |
78e64e1f60 | ||
![]() |
23a9b79320 | ||
![]() |
76394ce341 | ||
![]() |
1935df1faa | ||
![]() |
5af4ce28ab | ||
![]() |
ce8ee569c4 | ||
![]() |
b0508f430e | ||
![]() |
2139a80a7a | ||
![]() |
aa4bc2ce03 | ||
![]() |
fa65f84e09 | ||
![]() |
c06357a351 | ||
![]() |
092a02a624 | ||
![]() |
b9699f745f | ||
![]() |
7fa9f10c30 | ||
![]() |
7bf0655dae | ||
![]() |
96c5fdcbeb | ||
![]() |
c2e6d40382 | ||
![]() |
810d2a1ceb | ||
![]() |
af74f21af9 | ||
![]() |
cdf7558a8e | ||
![]() |
41b86e6c10 | ||
![]() |
3039c678a5 | ||
![]() |
498882d014 | ||
![]() |
6c2b8c2abb | ||
![]() |
e955cc4378 | ||
![]() |
eb96dd4803 | ||
![]() |
e0bdef98a6 | ||
![]() |
1130007d14 | ||
![]() |
d99d092784 | ||
![]() |
e3b18a33ca | ||
![]() |
1890aab1e6 | ||
![]() |
42bf350034 | ||
![]() |
5ff52ea113 | ||
![]() |
432e3ba636 | ||
![]() |
f7ab52fe9a | ||
![]() |
ad8430049d | ||
![]() |
2dffe7ba9e | ||
![]() |
5b8f97e0f6 | ||
![]() |
b3a763a48d | ||
![]() |
07569f10b5 | ||
![]() |
7c5a78a1cf | ||
![]() |
0e021e7d7d | ||
![]() |
b30ee884a7 | ||
![]() |
869b7c85ca | ||
![]() |
4d0d1ed2a1 | ||
![]() |
291983e4c3 | ||
![]() |
909cff2158 | ||
![]() |
4e676b1dba | ||
![]() |
9149bb9333 | ||
![]() |
4631994f20 | ||
![]() |
82e9178320 | ||
![]() |
67b4688168 | ||
![]() |
6e0e169b6e | ||
![]() |
100ba8edfa | ||
![]() |
d7448ecb95 | ||
![]() |
8b1801f378 | ||
![]() |
01a4d57566 | ||
![]() |
7edc9064d9 | ||
![]() |
30c47a65f4 | ||
![]() |
0889f42a00 |
60
.github/workflows/codeql-analysis.yml
vendored
Normal file
60
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
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
|
@@ -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() {
|
||||
|
73
gallery/src/components/more-info-content.ts
Normal file
73
gallery/src/components/more-info-content.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
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);
|
@@ -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", {
|
||||
|
@@ -16,6 +16,7 @@ import "../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
HassioResponse,
|
||||
ignoredStatusCodes,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
@@ -166,7 +167,7 @@ export class HassioUpdate extends LitElement {
|
||||
} 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 && ![502, 503, 504].includes(err.status_code)) {
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Update failed",
|
||||
text: extractApiErrorMessage(err),
|
||||
|
@@ -19,7 +19,10 @@ 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 } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoredStatusCodes,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
@@ -245,10 +248,13 @@ class HassioHostInfo extends LitElement {
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text: extractApiErrorMessage(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),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
@@ -272,10 +278,13 @@ class HassioHostInfo extends LitElement {
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text: extractApiErrorMessage(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),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200907.0",
|
||||
version="20200912.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
9
src/common/config/components_with_service.ts
Normal file
9
src/common/config/components_with_service.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
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]);
|
@@ -44,7 +44,6 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"script",
|
||||
"sun",
|
||||
"timer",
|
||||
"updater",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
"weather",
|
||||
|
@@ -105,12 +105,12 @@ const processTheme = (
|
||||
const keys = {};
|
||||
for (const key of Object.keys(combinedTheme)) {
|
||||
const prefixedKey = `--${key}`;
|
||||
const value = combinedTheme[key]!;
|
||||
const value = String(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;
|
||||
}
|
||||
|
@@ -3,49 +3,51 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
/** Return an icon representing a binary sensor state. */
|
||||
|
||||
export const binarySensorIcon = (state: HassEntity) => {
|
||||
const activated = state.state && state.state === "off";
|
||||
const is_off = state.state && state.state === "off";
|
||||
switch (state.attributes.device_class) {
|
||||
case "battery":
|
||||
return activated ? "hass:battery" : "hass:battery-outline";
|
||||
return is_off ? "hass:battery" : "hass:battery-outline";
|
||||
case "battery_charging":
|
||||
return is_off ? "hass:battery" : "hass:battery-charging";
|
||||
case "cold":
|
||||
return activated ? "hass:thermometer" : "hass:snowflake";
|
||||
return is_off ? "hass:thermometer" : "hass:snowflake";
|
||||
case "connectivity":
|
||||
return activated ? "hass:server-network-off" : "hass:server-network";
|
||||
return is_off ? "hass:server-network-off" : "hass:server-network";
|
||||
case "door":
|
||||
return activated ? "hass:door-closed" : "hass:door-open";
|
||||
return is_off ? "hass:door-closed" : "hass:door-open";
|
||||
case "garage_door":
|
||||
return activated ? "hass:garage" : "hass:garage-open";
|
||||
return is_off ? "hass:garage" : "hass:garage-open";
|
||||
case "gas":
|
||||
case "power":
|
||||
case "problem":
|
||||
case "safety":
|
||||
case "smoke":
|
||||
return activated ? "hass:shield-check" : "hass:alert";
|
||||
return is_off ? "hass:shield-check" : "hass:alert";
|
||||
case "heat":
|
||||
return activated ? "hass:thermometer" : "hass:fire";
|
||||
return is_off ? "hass:thermometer" : "hass:fire";
|
||||
case "light":
|
||||
return activated ? "hass:brightness-5" : "hass:brightness-7";
|
||||
return is_off ? "hass:brightness-5" : "hass:brightness-7";
|
||||
case "lock":
|
||||
return activated ? "hass:lock" : "hass:lock-open";
|
||||
return is_off ? "hass:lock" : "hass:lock-open";
|
||||
case "moisture":
|
||||
return activated ? "hass:water-off" : "hass:water";
|
||||
return is_off ? "hass:water-off" : "hass:water";
|
||||
case "motion":
|
||||
return activated ? "hass:walk" : "hass:run";
|
||||
return is_off ? "hass:walk" : "hass:run";
|
||||
case "occupancy":
|
||||
return activated ? "hass:home-outline" : "hass:home";
|
||||
return is_off ? "hass:home-outline" : "hass:home";
|
||||
case "opening":
|
||||
return activated ? "hass:square" : "hass:square-outline";
|
||||
return is_off ? "hass:square" : "hass:square-outline";
|
||||
case "plug":
|
||||
return activated ? "hass:power-plug-off" : "hass:power-plug";
|
||||
return is_off ? "hass:power-plug-off" : "hass:power-plug";
|
||||
case "presence":
|
||||
return activated ? "hass:home-outline" : "hass:home";
|
||||
return is_off ? "hass:home-outline" : "hass:home";
|
||||
case "sound":
|
||||
return activated ? "hass:music-note-off" : "hass:music-note";
|
||||
return is_off ? "hass:music-note-off" : "hass:music-note";
|
||||
case "vibration":
|
||||
return activated ? "hass:crop-portrait" : "hass:vibrate";
|
||||
return is_off ? "hass:crop-portrait" : "hass:vibrate";
|
||||
case "window":
|
||||
return activated ? "hass:window-closed" : "hass:window-open";
|
||||
return is_off ? "hass:window-closed" : "hass:window-open";
|
||||
default:
|
||||
return activated ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
|
||||
return is_off ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
|
||||
}
|
||||
};
|
||||
|
@@ -3,9 +3,10 @@ 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";
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,12 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import durationToSeconds from "../datetime/duration_to_seconds";
|
||||
|
||||
export const timerTimeRemaining = (stateObj: HassEntity) => {
|
||||
export const timerTimeRemaining = (
|
||||
stateObj: HassEntity
|
||||
): undefined | number => {
|
||||
if (!stateObj.attributes.remaining) {
|
||||
return undefined;
|
||||
}
|
||||
let timeRemaining = durationToSeconds(stateObj.attributes.remaining);
|
||||
|
||||
if (stateObj.state === "active") {
|
||||
|
50
src/common/util/throttle.ts
Normal file
50
src/common/util/throttle.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
};
|
@@ -6,6 +6,7 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
@@ -51,7 +52,8 @@ const rowRenderer = (
|
||||
root.querySelector("[secondary]")!.textContent = model.item.entity_id;
|
||||
};
|
||||
|
||||
class HaEntityPicker extends LitElement {
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
@@ -276,8 +278,6 @@ class HaEntityPicker extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-entity-picker", HaEntityPicker);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-picker": HaEntityPicker;
|
||||
|
@@ -20,6 +20,7 @@ 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 {
|
||||
@@ -81,7 +82,8 @@ 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>
|
||||
@@ -108,7 +110,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
|
||||
@@ -121,7 +123,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
}
|
||||
|
||||
private _computeIcon(domain: string, state: HassEntity) {
|
||||
if (state.state === "unavailable") {
|
||||
if (state.state === UNAVAILABLE) {
|
||||
return null;
|
||||
}
|
||||
switch (domain) {
|
||||
@@ -166,7 +168,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
|
||||
|
@@ -26,7 +26,11 @@ class HaCameraStream extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj?: CameraEntity;
|
||||
|
||||
@property({ type: Boolean }) public showControls = false;
|
||||
@property({ type: Boolean, attribute: "controls" })
|
||||
public controls = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "muted" })
|
||||
public muted = false;
|
||||
|
||||
// We keep track if we should force MJPEG with a string
|
||||
// that way it automatically resets if we change entity.
|
||||
@@ -35,7 +39,7 @@ class HaCameraStream extends LitElement {
|
||||
@internalProperty() private _url?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.stateObj || (!this._forceMJPEG && !this._url)) {
|
||||
if (!this.stateObj) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -52,21 +56,23 @@ class HaCameraStream extends LitElement {
|
||||
)} camera.`}
|
||||
/>
|
||||
`
|
||||
: html`
|
||||
: this._url
|
||||
? html`
|
||||
<ha-hls-player
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
?controls=${this.showControls}
|
||||
.muted=${this.muted}
|
||||
.controls=${this.controls}
|
||||
.hass=${this.hass}
|
||||
.url=${this._url!}
|
||||
.url=${this._url}
|
||||
></ha-hls-player>
|
||||
`}
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("stateObj")) {
|
||||
if (changedProps.has("stateObj") && !this._shouldRenderMJPEG) {
|
||||
this._forceMJPEG = undefined;
|
||||
this._getStreamUrl();
|
||||
}
|
||||
|
@@ -97,6 +97,7 @@ 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);
|
||||
|
@@ -176,6 +176,11 @@ 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)
|
||||
);
|
||||
@@ -319,6 +324,9 @@ 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);
|
||||
@@ -330,6 +338,9 @@ 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%, ${
|
||||
|
@@ -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}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
property,
|
||||
SVGTemplateResult,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
@customElement("ha-settings-row")
|
||||
export class HaSettingsRow extends LitElement {
|
||||
@@ -49,6 +49,9 @@ export class HaSettingsRow extends LitElement {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
::slotted(ha-switch) {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,6 @@ import memoizeOne from "memoize-one";
|
||||
import { LocalStorage } from "../common/decorators/local-storage";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { ActionHandlerDetail } from "../data/lovelace";
|
||||
@@ -166,7 +165,7 @@ let sortStyles: CSSResult;
|
||||
class HaSidebar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public alwaysExpand = false;
|
||||
|
||||
@@ -235,7 +234,14 @@ class HaSidebar extends LitElement {
|
||||
</style>
|
||||
`
|
||||
: ""}
|
||||
<div class="menu">
|
||||
<div
|
||||
class="menu"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: !this._editMode,
|
||||
disabled: this._editMode,
|
||||
})}
|
||||
>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
@@ -253,7 +259,7 @@ class HaSidebar extends LitElement {
|
||||
<div class="title">
|
||||
${this._editMode
|
||||
? html`<mwc-button outlined @click=${this._closeEditMode}>
|
||||
DONE
|
||||
${hass.localize("ui.sidebar.done")}
|
||||
</mwc-button>`
|
||||
: "Home Assistant"}
|
||||
</div>
|
||||
@@ -266,11 +272,6 @@ 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">
|
||||
@@ -286,27 +287,29 @@ 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 === "lovelace"
|
||||
.icon=${panel.url_path === this.hass.defaultPanel
|
||||
? "mdi:view-dashboard"
|
||||
: panel.icon}
|
||||
></ha-icon>
|
||||
<span class="item-text"
|
||||
>${panel.url_path === "lovelace"
|
||||
>${panel.url_path === this.hass.defaultPanel
|
||||
? hass.localize("panel.states")
|
||||
: hass.localize(`panel.${panel.title}`) ||
|
||||
panel.title}</span
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="hide-panel"
|
||||
.panel=${url}
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
<mwc-icon-button class="hide-panel">
|
||||
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-icon-item>`;
|
||||
})}
|
||||
<div class="spacer" disabled></div>
|
||||
@@ -446,6 +449,9 @@ class HaSidebar extends LitElement {
|
||||
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||
this._notifications = notifications;
|
||||
});
|
||||
window.addEventListener("hass-edit-sidebar", () =>
|
||||
this._activateEditMode()
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
@@ -478,11 +484,15 @@ class HaSidebar extends LitElement {
|
||||
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
|
||||
}
|
||||
|
||||
private async _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
|
||||
private _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"),
|
||||
@@ -497,6 +507,8 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
this._editMode = true;
|
||||
|
||||
fireEvent(this, "hass-open-menu");
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
this._createSortable();
|
||||
@@ -521,7 +533,7 @@ class HaSidebar extends LitElement {
|
||||
|
||||
private async _hidePanel(ev: Event) {
|
||||
ev.preventDefault();
|
||||
const panel = (ev.target as any).panel;
|
||||
const panel = (ev.currentTarget as any).panel;
|
||||
if (this._hiddenPanels.includes(panel)) {
|
||||
return;
|
||||
}
|
||||
@@ -534,7 +546,7 @@ class HaSidebar extends LitElement {
|
||||
|
||||
private async _unhidePanel(ev: Event) {
|
||||
ev.preventDefault();
|
||||
const index = this._hiddenPanels.indexOf((ev.target as any).panel);
|
||||
const index = this._hiddenPanels.indexOf((ev.currentTarget as any).panel);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
@@ -646,19 +658,17 @@ class HaSidebar extends LitElement {
|
||||
return panels.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
panel.url_path === "lovelace"
|
||||
? this.hass.localize("panel.states")
|
||||
panel.url_path === this.hass.defaultPanel
|
||||
? panel.title || this.hass.localize("panel.states")
|
||||
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.url_path === "lovelace" ? undefined : panel.icon,
|
||||
panel.url_path === "lovelace" ? mdiViewDashboard : undefined
|
||||
panel.icon,
|
||||
panel.url_path === this.hass.defaultPanel && !panel.icon
|
||||
? mdiViewDashboard
|
||||
: undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _handlePanelTap(ev: Event) {
|
||||
navigate(this, (ev.currentTarget as HTMLAnchorElement).href);
|
||||
}
|
||||
|
||||
private _renderPanel(
|
||||
urlPath: string,
|
||||
title: string | null,
|
||||
@@ -668,10 +678,9 @@ class HaSidebar extends LitElement {
|
||||
return html`
|
||||
<a
|
||||
aria-role="option"
|
||||
href="${`/${urlPath}`}"
|
||||
data-panel="${urlPath}"
|
||||
href=${`/${urlPath}`}
|
||||
data-panel=${urlPath}
|
||||
tabindex="-1"
|
||||
@tap=${this._handlePanelTap}
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
@@ -684,12 +693,13 @@ class HaSidebar extends LitElement {
|
||||
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
|
||||
<span class="item-text">${title}</span>
|
||||
${this._editMode
|
||||
? html`<ha-svg-icon
|
||||
? html`<mwc-icon-button
|
||||
class="hide-panel"
|
||||
.panel=${urlPath}
|
||||
@click=${this._hidePanel}
|
||||
.path=${mdiClose}
|
||||
></ha-svg-icon>`
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>`
|
||||
: ""}
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
@@ -758,6 +768,9 @@ class HaSidebar extends LitElement {
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
:host([narrow]) .title {
|
||||
padding: 0 16px;
|
||||
}
|
||||
:host([expanded]) .title {
|
||||
display: initial;
|
||||
}
|
||||
|
@@ -279,6 +279,7 @@ class LocationEditor extends LitElement {
|
||||
}
|
||||
#map {
|
||||
height: 100%;
|
||||
background: inherit;
|
||||
}
|
||||
.leaflet-edit-move {
|
||||
border-radius: 50%;
|
||||
|
@@ -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, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@@ -19,15 +19,14 @@ import {
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import {
|
||||
browseLocalMediaPlayer,
|
||||
browseMediaPlayer,
|
||||
BROWSER_SOURCE,
|
||||
MediaClassBrowserSettings,
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
} from "../../data/media-player";
|
||||
@@ -94,34 +93,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
this._navigate(item);
|
||||
}
|
||||
|
||||
private _renderError(err: { message: string; code: string }) {
|
||||
if (err.message === "Media directory does not exist.") {
|
||||
return html`
|
||||
<h2>No local media found.</h2>
|
||||
<p>
|
||||
It looks like you have not yet created a media directory.
|
||||
<br />Create a directory with the name <b>"media"</b> in the
|
||||
configuration directory of Home Assistant
|
||||
(${this.hass.config.config_dir}). <br />Place your video, audio and
|
||||
image files in this directory to be able to browse and play them in
|
||||
the browser or on supported media players.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Check the
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/media_source/#local-media"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>documentation</a
|
||||
>
|
||||
for more info
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return err.message;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._loading) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
@@ -137,7 +108,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
text: this._renderError(this._error),
|
||||
});
|
||||
} else {
|
||||
return html`<div class="container error">
|
||||
return html`<div class="container">
|
||||
${this._renderError(this._error)}
|
||||
</div>`;
|
||||
}
|
||||
@@ -156,120 +127,126 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
? this._mediaPlayerItems[this._mediaPlayerItems.length - 2]
|
||||
: undefined;
|
||||
|
||||
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 subtitle = this.hass.localize(
|
||||
`ui.components.media-browser.class.${currentItem.media_class}`
|
||||
);
|
||||
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-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`
|
||||
<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}`
|
||||
)}
|
||||
.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
|
||||
</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)
|
||||
? 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>
|
||||
${mediaType
|
||||
? html`
|
||||
<h2 class="subtitle">
|
||||
${mediaType}
|
||||
</h2>
|
||||
<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>
|
||||
${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>
|
||||
${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>
|
||||
${this._error
|
||||
? html`<div class="container error">
|
||||
${this._renderError(this._error)}
|
||||
</div>`
|
||||
? html`
|
||||
<div class="container error">
|
||||
${this._renderError(this._error)}
|
||||
</div>
|
||||
`
|
||||
: currentItem.children?.length
|
||||
? hasExpandableChildren
|
||||
? childrenMediaClass.layout === "grid"
|
||||
? html`
|
||||
<div class="children">
|
||||
<div
|
||||
class="children ${classMap({
|
||||
portrait: childrenMediaClass.thumbnail_ratio === "portrait",
|
||||
})}"
|
||||
>
|
||||
${currentItem.children.map(
|
||||
(child) => html`
|
||||
<div
|
||||
@@ -286,11 +263,16 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
: "none",
|
||||
})}
|
||||
>
|
||||
${child.can_expand && !child.thumbnail
|
||||
${!child.thumbnail
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class="folder"
|
||||
.path=${mdiFolder}
|
||||
.path=${MediaClassBrowserSettings[
|
||||
child.media_class === "directory"
|
||||
? child.children_media_class ||
|
||||
child.media_class
|
||||
: child.media_class
|
||||
].icon}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
@@ -298,7 +280,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${child.can_play
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
class="play"
|
||||
class="play ${classMap({
|
||||
can_expand: child.can_expand,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
@@ -330,15 +314,16 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${currentItem.children.map(
|
||||
(child) => html`
|
||||
<mwc-list-item
|
||||
@click=${this._actionClicked}
|
||||
@click=${this._childClicked}
|
||||
.item=${child}
|
||||
graphic="avatar"
|
||||
hasMeta
|
||||
dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<div
|
||||
class="graphic"
|
||||
style=${ifDefined(
|
||||
showImages && child.thumbnail
|
||||
mediaClass.show_list_images && child.thumbnail
|
||||
? `background-image: url(${child.thumbnail})`
|
||||
: undefined
|
||||
)}
|
||||
@@ -346,7 +331,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
>
|
||||
<mwc-icon-button
|
||||
class="play ${classMap({
|
||||
show: !showImages || !child.thumbnail,
|
||||
show:
|
||||
!mediaClass.show_list_images || !child.thumbnail,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
@@ -359,16 +345,18 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</div>
|
||||
<span>${child.title}</span>
|
||||
<span class="title">${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>`}
|
||||
: html`
|
||||
<div class="container">
|
||||
${this.hass.localize("ui.components.media-browser.no_items")}
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -476,13 +464,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
mediaContentType
|
||||
)
|
||||
: await browseLocalMediaPlayer(this.hass, mediaContentId);
|
||||
itemData.children = itemData.children?.sort((first, second) =>
|
||||
!first.can_expand && second.can_expand
|
||||
? 1
|
||||
: first.can_expand && !second.can_expand
|
||||
? -1
|
||||
: compare(first.title, second.title)
|
||||
);
|
||||
|
||||
return itemData;
|
||||
}
|
||||
@@ -510,14 +491,38 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
this._resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
private _hasExpandableChildren = memoizeOne((children?: MediaPlayerItem[]) =>
|
||||
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,19 +540,21 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
display: block;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.header {
|
||||
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;
|
||||
@@ -575,6 +582,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
.header-info mwc-button {
|
||||
display: block;
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
@@ -618,6 +626,7 @@ 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;
|
||||
}
|
||||
@@ -634,13 +643,20 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(var(--media-browse-item-size, 175px), 0.33fr)
|
||||
minmax(var(--media-browse-item-size, 175px), 0.1fr)
|
||||
);
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
.child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -652,7 +668,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
.children ha-card {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
@@ -660,6 +676,11 @@ 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,
|
||||
@@ -675,24 +696,43 @@ 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:hover {
|
||||
.ha-card-parent:hover ha-card {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.child .title {
|
||||
font-size: 16px;
|
||||
padding-top: 8px;
|
||||
padding-left: 2px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
@@ -702,6 +742,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
.child .type {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
mwc-list-item .graphic {
|
||||
@@ -726,6 +767,14 @@ 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]) {
|
||||
@@ -740,6 +789,10 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .header.no-dialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host([narrow]) .header_button {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
|
@@ -39,6 +39,9 @@ class PersonBadge extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
.picture {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
@@ -104,6 +104,9 @@ class UserBadge extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
.picture {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
@@ -13,3 +13,5 @@ export const extractApiErrorMessage = (error: any): string => {
|
||||
: error.body || "Unknown error, see logs"
|
||||
: error;
|
||||
};
|
||||
|
||||
export const ignoredStatusCodes = new Set([502, 503, 504]);
|
||||
|
@@ -1,5 +1,23 @@
|
||||
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;
|
||||
@@ -22,6 +40,66 @@ 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;
|
||||
}
|
||||
@@ -40,6 +118,8 @@ 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;
|
||||
|
17
src/data/refresh_token.ts
Normal file
17
src/data/refresh_token.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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";
|
||||
}
|
@@ -200,7 +200,7 @@ export const weatherSVGStyles = css`
|
||||
fill: var(--weather-icon-sun-color, #fdd93c);
|
||||
}
|
||||
.moon {
|
||||
fill: var(--weather-icon-moon-color, #fdf9cc);
|
||||
fill: var(--weather-icon-moon-color, #fcf497);
|
||||
}
|
||||
.cloud-back {
|
||||
fill: var(--weather-icon-cloud-back-color, #d4d4d4);
|
||||
|
@@ -1,20 +1,27 @@
|
||||
import { Connection, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
|
||||
interface RenderTemplateResult {
|
||||
export interface RenderTemplateResult {
|
||||
result: string;
|
||||
listeners: TemplateListeners;
|
||||
}
|
||||
|
||||
interface TemplateListeners {
|
||||
all: boolean;
|
||||
domains: string[];
|
||||
entities: string[];
|
||||
}
|
||||
|
||||
export const subscribeRenderTemplate = (
|
||||
conn: Connection,
|
||||
onChange: (result: string) => void,
|
||||
onChange: (result: RenderTemplateResult) => void,
|
||||
params: {
|
||||
template: string;
|
||||
entity_ids?: string | string[];
|
||||
variables?: object;
|
||||
}
|
||||
): Promise<UnsubscribeFunc> => {
|
||||
return conn.subscribeMessage(
|
||||
(msg: RenderTemplateResult) => onChange(msg.result),
|
||||
{ type: "render_template", ...params }
|
||||
);
|
||||
return conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), {
|
||||
type: "render_template",
|
||||
...params,
|
||||
});
|
||||
};
|
||||
|
@@ -97,8 +97,13 @@ export const showConfigFlowDialog = (
|
||||
},
|
||||
|
||||
renderExternalStepHeader(hass, step) {
|
||||
return hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.title`
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.title`
|
||||
) ||
|
||||
hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.external_step.open_site"
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
|
@@ -57,7 +57,8 @@ class DialogBox extends LitElement {
|
||||
open
|
||||
?scrimClickAction=${this._params.prompt}
|
||||
?escapeKeyAction=${this._params.prompt}
|
||||
@closed=${this._dismiss}
|
||||
@closed=${this._dialogClosed}
|
||||
defaultAction="ignore"
|
||||
.heading=${this._params.title
|
||||
? this._params.title
|
||||
: this._params.confirmation &&
|
||||
@@ -78,10 +79,10 @@ class DialogBox extends LitElement {
|
||||
${this._params.prompt
|
||||
? html`
|
||||
<paper-input
|
||||
autofocus
|
||||
dialogInitialFocus
|
||||
.value=${this._value}
|
||||
@value-changed=${this._valueChanged}
|
||||
@keyup=${this._handleKeyUp}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._params.inputLabel
|
||||
? this._params.inputLabel
|
||||
: ""}
|
||||
@@ -100,7 +101,11 @@ class DialogBox extends LitElement {
|
||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-button @click=${this._confirm} slot="primaryAction">
|
||||
<mwc-button
|
||||
@click=${this._confirm}
|
||||
?dialogInitialFocus=${!this._params.prompt}
|
||||
slot="primaryAction"
|
||||
>
|
||||
${this._params.confirmText
|
||||
? this._params.confirmText
|
||||
: this.hass.localize("ui.dialogs.generic.ok")}
|
||||
@@ -133,7 +138,17 @@ 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 });
|
||||
}
|
||||
|
@@ -12,12 +12,13 @@ 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() public stateObj?: HassEntity;
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
@@ -34,7 +35,10 @@ class MoreInfoAutomation extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<mwc-button @click=${this.handleAction}>
|
||||
<mwc-button
|
||||
@click=${this.handleAction}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj!.state)}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
@@ -52,7 +56,7 @@ class MoreInfoAutomation extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.actions {
|
||||
margin: 36px 0 8px 0;
|
||||
margin: 8px 0;
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
@@ -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}"
|
||||
showcontrols
|
||||
.stateObj=${this.stateObj}
|
||||
controls
|
||||
></ha-camera-stream>
|
||||
${this._cameraPrefs
|
||||
? html`
|
||||
|
@@ -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.value;
|
||||
const newVal = ev.detail.item.itemName;
|
||||
|
||||
if (!newVal || this.stateObj!.attributes.effect === newVal) {
|
||||
return;
|
||||
|
@@ -188,14 +188,17 @@ 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" @click=${
|
||||
this._sendTTS
|
||||
}></ha-icon-button>
|
||||
<ha-icon-button
|
||||
icon="hass:send"
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
@click=${this._sendTTS}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
@@ -26,15 +26,12 @@ class MoreInfoTimer extends LitElement {
|
||||
return html`
|
||||
<ha-attributes
|
||||
.stateObj=${this.stateObj}
|
||||
.extraFilters=${"remaining"}
|
||||
extra-filters="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>
|
||||
`
|
||||
@@ -42,7 +39,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")}
|
||||
@@ -52,13 +49,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")}
|
||||
|
@@ -13,10 +13,15 @@ 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 } from "../../common/const";
|
||||
import {
|
||||
DOMAINS_MORE_INFO_NO_HISTORY,
|
||||
DOMAINS_WITH_MORE_INFO,
|
||||
} from "../../common/const";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
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";
|
||||
@@ -29,12 +34,39 @@ import { haStyleDialog } from "../../resources/styles";
|
||||
import "../../state-summary/state-card-content";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import "./more-info-content";
|
||||
import "./ha-more-info-history";
|
||||
import "./ha-more-info-logbook";
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -47,6 +79,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
@internalProperty() private _entityId?: string | null;
|
||||
|
||||
@internalProperty() private _moreInfoType?: string;
|
||||
|
||||
@internalProperty() private _currTabIndex = 0;
|
||||
|
||||
public showDialog(params: MoreInfoDialogParams) {
|
||||
@@ -63,6 +97,23 @@ 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``;
|
||||
@@ -127,7 +178,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</ha-header-bar>
|
||||
${this._computeShowHistoryComponent(entityId)
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) &&
|
||||
this._computeShowHistoryComponent(entityId)
|
||||
? html`
|
||||
<mwc-tab-bar
|
||||
.activeIndex=${this._currTabIndex}
|
||||
@@ -135,7 +187,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
>
|
||||
<mwc-tab
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.controls"
|
||||
"ui.dialogs.more_info_control.details"
|
||||
)}
|
||||
></mwc-tab>
|
||||
<mwc-tab
|
||||
@@ -160,10 +212,23 @@ export class MoreInfoDialog extends LitElement {
|
||||
.hass=${this.hass}
|
||||
></state-card-content>
|
||||
`}
|
||||
<more-info-content
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></more-info-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,
|
||||
})
|
||||
: ""}
|
||||
${stateObj.attributes.restored
|
||||
? html`
|
||||
<p>
|
||||
@@ -188,10 +253,14 @@ export class MoreInfoDialog extends LitElement {
|
||||
: ""}
|
||||
`
|
||||
: html`
|
||||
<ha-more-info-tab-history
|
||||
<ha-more-info-history
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-tab-history>
|
||||
></ha-more-info-history>
|
||||
<ha-more-info-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-logbook>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
@@ -199,17 +268,14 @@ 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, "history") ||
|
||||
isComponentLoaded(this.hass, "logbook")) &&
|
||||
!DOMAINS_MORE_INFO_NO_HISTORY.includes(computeDomain(entityId))
|
||||
);
|
||||
}
|
||||
@@ -274,6 +340,7 @@ 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) {
|
||||
|
109
src/dialogs/more-info/ha-more-info-history.ts
Normal file
109
src/dialogs/more-info/ha-more-info-history.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
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;
|
||||
}
|
||||
}
|
171
src/dialogs/more-info/ha-more-info-logbook.ts
Normal file
171
src/dialogs/more-info/ha-more-info-logbook.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,166 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,73 +0,0 @@
|
||||
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);
|
@@ -7,5 +7,3 @@ import "../util/legacy-support";
|
||||
setPassiveTouchGestures(true);
|
||||
|
||||
(window as any).frontendVersion = __VERSION__;
|
||||
|
||||
import("../resources/html-import/polyfill");
|
||||
|
@@ -100,9 +100,5 @@
|
||||
{% endfor -%}
|
||||
}
|
||||
</script>
|
||||
|
||||
{% for extra_url in extra_urls -%}
|
||||
<link rel="import" href="{{ extra_url }}" async />
|
||||
{% endfor -%}
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -11,7 +11,12 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #111111;
|
||||
color: var(--primary-text-color, #e1e1e1);
|
||||
color: #e1e1e1;
|
||||
}
|
||||
ha-onboarding {
|
||||
--primary-text-color: #e1e1e1;
|
||||
--secondary-text-color: #9b9b9b;
|
||||
--disabled-text-color: #6f6f6f;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
|
@@ -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 { navigate } from "../common/navigate";
|
||||
import "../components/ha-menu-button";
|
||||
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 { 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 { HomeAssistant, Route } from "../types";
|
||||
|
||||
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> `
|
||||
: ""}
|
||||
${tabs.length > 1 || !this.narrow
|
||||
${showTabs
|
||||
? html`
|
||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||
${tabs}
|
||||
@@ -163,10 +163,15 @@ class HassTabsSubpage extends LitElement {
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" @scroll=${this._saveScrollPos}>
|
||||
<div
|
||||
class="content ${classMap({ tabs: showTabs })}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div id="fab"><slot name="fab"></slot></div>
|
||||
<div id="fab" class="${classMap({ tabs: showTabs })}">
|
||||
<slot name="fab"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -274,12 +279,13 @@ 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 {
|
||||
:host([narrow]) .content.tabs {
|
||||
height: calc(100% - 128px);
|
||||
height: calc(100% - 128px - env(safe-area-inset-bottom));
|
||||
}
|
||||
@@ -290,7 +296,7 @@ class HassTabsSubpage extends LitElement {
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
:host([narrow]) #fab {
|
||||
:host([narrow]) #fab.tabs {
|
||||
bottom: calc(84px + env(safe-area-inset-bottom));
|
||||
}
|
||||
#fab[is-wide] {
|
||||
|
@@ -24,6 +24,7 @@ const NON_SWIPABLE_PANELS = ["map"];
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"hass-open-menu": undefined;
|
||||
"hass-toggle-menu": undefined;
|
||||
"hass-show-notifications": undefined;
|
||||
}
|
||||
@@ -92,6 +93,17 @@ 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) {
|
||||
|
@@ -6,9 +6,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
@@ -173,7 +173,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
<mwc-list-item class="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
@@ -336,7 +336,6 @@ 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 {
|
||||
|
@@ -19,6 +19,7 @@ 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";
|
||||
|
||||
export interface ConditionElement extends LitElement {
|
||||
condition: Condition;
|
||||
@@ -86,7 +87,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
<mwc-list-item class="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
@@ -134,20 +135,23 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
this._yamlMode = !this._yamlMode;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
`;
|
||||
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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -104,6 +104,7 @@ export class HaAutomationEditor extends LitElement {
|
||||
<ha-svg-icon .path=${mdiContentDuplicate}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-icon-button
|
||||
class="warning"
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.delete_automation"
|
||||
|
@@ -25,6 +25,7 @@ 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";
|
||||
@@ -35,9 +36,9 @@ import { showThingtalkDialog } from "./show-dialog-thingtalk";
|
||||
class HaAutomationPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public isWide!: boolean;
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@@ -58,7 +59,7 @@ class HaAutomationPicker extends LitElement {
|
||||
toggle: {
|
||||
title: "",
|
||||
type: "icon",
|
||||
template: (_toggle, automation) =>
|
||||
template: (_toggle, automation: any) =>
|
||||
html`
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
@@ -91,10 +92,11 @@ class HaAutomationPicker extends LitElement {
|
||||
if (!narrow) {
|
||||
columns.execute = {
|
||||
title: "",
|
||||
template: (_info, automation) => html`
|
||||
template: (_info, automation: any) => 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>
|
||||
|
@@ -118,7 +118,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
<mwc-list-item class="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
|
@@ -103,7 +103,9 @@ class CloudAlexa extends LitElement {
|
||||
|
||||
this._entities.forEach((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
const config = this._entityConfigs[entity.entity_id] || {};
|
||||
const config = this._entityConfigs[entity.entity_id] || {
|
||||
should_expose: null,
|
||||
};
|
||||
const isExposed = emptyFilter
|
||||
? this._configIsExposed(entity.entity_id, config)
|
||||
: filterFunc(entity.entity_id);
|
||||
@@ -319,9 +321,7 @@ class CloudAlexa extends LitElement {
|
||||
}
|
||||
|
||||
private _configIsExposed(entityId: string, config: AlexaEntityConfig) {
|
||||
return config.should_expose === null
|
||||
? this._configIsDomainExposed(entityId)
|
||||
: config.should_expose;
|
||||
return config.should_expose ?? this._configIsDomainExposed(entityId);
|
||||
}
|
||||
|
||||
private async _exposeChanged(ev: CustomEvent<ActionDetail>) {
|
||||
|
@@ -109,7 +109,9 @@ class CloudGoogleAssistant extends LitElement {
|
||||
|
||||
this._entities.forEach((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
const config = this._entityConfigs[entity.entity_id] || {};
|
||||
const config = this._entityConfigs[entity.entity_id] || {
|
||||
should_expose: null,
|
||||
};
|
||||
const isExposed = emptyFilter
|
||||
? this._configIsExposed(entity.entity_id, config)
|
||||
: filterFunc(entity.entity_id);
|
||||
@@ -324,9 +326,7 @@ class CloudGoogleAssistant extends LitElement {
|
||||
}
|
||||
|
||||
private _configIsExposed(entityId: string, config: GoogleEntityConfig) {
|
||||
return config.should_expose === null
|
||||
? this._configIsDomainExposed(entityId)
|
||||
: config.should_expose;
|
||||
return config.should_expose ?? this._configIsDomainExposed(entityId);
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
|
@@ -58,6 +58,8 @@ 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;
|
||||
@@ -280,7 +282,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) {
|
||||
@@ -378,7 +380,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.entities.picker.disable_selected.button"
|
||||
)}</mwc-button
|
||||
>
|
||||
<mwc-button @click=${this._removeSelected}
|
||||
<mwc-button @click=${this._removeSelected} class="warning"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.remove_selected.button"
|
||||
)}</mwc-button
|
||||
@@ -406,6 +408,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</paper-tooltip>
|
||||
<ha-icon-button
|
||||
class="warning"
|
||||
id="remove-btn"
|
||||
icon="hass:delete"
|
||||
@click=${this._removeSelected}
|
||||
@@ -721,111 +724,114 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
navigate(this, window.location.pathname, true);
|
||||
}
|
||||
|
||||
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: "";
|
||||
}
|
||||
`;
|
||||
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: "";
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -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,7 +117,6 @@ 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>
|
||||
`;
|
||||
|
@@ -1,14 +1,21 @@
|
||||
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,
|
||||
internalProperty,
|
||||
query,
|
||||
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,
|
||||
@@ -16,12 +23,11 @@ import {
|
||||
IntegrationManifest,
|
||||
} from "../../../data/integration";
|
||||
import { getLoggedErrorIntegration } from "../../../data/system_log";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { 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;
|
||||
@@ -30,6 +36,8 @@ 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;
|
||||
@@ -66,13 +74,25 @@ class DialogSystemLogDetail extends LitElement {
|
||||
opened
|
||||
@opened-changed="${this._openedChanged}"
|
||||
>
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.logs.details",
|
||||
"level",
|
||||
item.level
|
||||
)}
|
||||
</h2>
|
||||
<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>
|
||||
<paper-dialog-scrollable>
|
||||
<p>
|
||||
Logger: ${item.name}<br />
|
||||
@@ -148,6 +168,25 @@ 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,
|
||||
@@ -164,6 +203,15 @@ 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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -198,6 +198,7 @@ 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"
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -18,12 +21,14 @@ 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";
|
||||
@@ -36,7 +41,7 @@ import { HaDeviceAction } from "../automation/action/types/ha-automation-action-
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiContentSave } from "@mdi/js";
|
||||
import { mdiContentSave, mdiDotsVertical } from "@mdi/js";
|
||||
import { PaperListboxElement } from "@polymer/paper-listbox";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
|
||||
@@ -61,6 +66,8 @@ export class HaScriptEditor extends LitElement {
|
||||
|
||||
@internalProperty() private _errors?: string;
|
||||
|
||||
@internalProperty() private _yamlMode = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
@@ -70,18 +77,46 @@ export class HaScriptEditor extends LitElement {
|
||||
.backCallback=${() => this._backTapped()}
|
||||
.tabs=${configSections.automation}
|
||||
>
|
||||
${!this.scriptEntityId
|
||||
? ""
|
||||
: html`
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.editor.delete_script"
|
||||
)}"
|
||||
icon="hass:delete"
|
||||
@click=${this._deleteConfirm}
|
||||
></ha-icon-button>
|
||||
`}
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<mwc-icon-button
|
||||
slot="trigger"
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item
|
||||
aria-label=${this._yamlMode
|
||||
? this.hass.localize("ui.panel.config.automation.editor.edit_ui")
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
>
|
||||
${this._yamlMode
|
||||
? this.hass.localize("ui.panel.config.automation.editor.edit_ui")
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${!this.scriptEntityId
|
||||
? ""
|
||||
: html`
|
||||
<mwc-list-item
|
||||
class="warning"
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.delete_script"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.delete_script"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`}
|
||||
</ha-button-menu>
|
||||
|
||||
${this.narrow
|
||||
? html` <span slot="header">${this._config?.alias}</span> `
|
||||
: ""}
|
||||
@@ -95,135 +130,195 @@ export class HaScriptEditor extends LitElement {
|
||||
})}"
|
||||
>
|
||||
${this._config
|
||||
? html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html` <span slot="header">${this._config.alias}</span> `
|
||||
: ""}
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.introduction"
|
||||
)}
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.alias"
|
||||
)}
|
||||
name="alias"
|
||||
.value=${this._config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
@change=${this._aliasChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<ha-icon-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.icon"
|
||||
)}
|
||||
.name=${"icon"}
|
||||
.value=${this._config.icon}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-icon-input>
|
||||
${!this.scriptEntityId
|
||||
? html` <paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.id"
|
||||
)}
|
||||
.errorMessage=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.id_already_exists"
|
||||
)}
|
||||
.invalid=${this._idError}
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._idChanged}
|
||||
? this._yamlMode
|
||||
? html`
|
||||
<ha-config-section .isWide=${false}>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
`
|
||||
: ""}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this._config}
|
||||
@value-changed=${this._onYamlChange}
|
||||
></ha-yaml-editor>
|
||||
</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>
|
||||
`
|
||||
: html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
`
|
||||
: ""}
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.introduction"
|
||||
)}
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.alias"
|
||||
)}
|
||||
name="alias"
|
||||
.value=${this._config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
@change=${this._aliasChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<ha-icon-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.icon"
|
||||
)}
|
||||
.name=${"icon"}
|
||||
.value=${this._config.icon}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-icon-input>
|
||||
${!this.scriptEntityId
|
||||
? html` <paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.id"
|
||||
)}
|
||||
.errorMessage=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.id_already_exists"
|
||||
)}
|
||||
.invalid=${this._idError}
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._idChanged}
|
||||
>
|
||||
</paper-input>`
|
||||
: ""}
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.modes.description",
|
||||
"documentation_link",
|
||||
html`<a
|
||||
href="https://www.home-assistant.io/integrations/script/#script-modes"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.script.editor.modes.documentation"
|
||||
)}</a
|
||||
>`
|
||||
)}
|
||||
</p>
|
||||
<paper-dropdown-menu-light
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.modes.label"
|
||||
)}
|
||||
no-animations
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._config.mode
|
||||
? MODES.indexOf(this._config.mode)
|
||||
: 0}
|
||||
@iron-select=${this._modeChanged}
|
||||
>
|
||||
</paper-input>`
|
||||
: ""}
|
||||
${MODES.map(
|
||||
(mode) => html`
|
||||
<paper-item .mode=${mode}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.script.editor.modes.${mode}`
|
||||
) || mode}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
${this._config.mode &&
|
||||
MODES_MAX.includes(this._config.mode)
|
||||
? html` <paper-input
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.script.editor.max.${this._config.mode}`
|
||||
)}
|
||||
type="number"
|
||||
name="max"
|
||||
.value=${this._config.max || "10"}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</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>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.sequence"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.modes.description",
|
||||
"documentation_link",
|
||||
html`<a
|
||||
href="https://www.home-assistant.io/integrations/script/#script-modes"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.script.editor.modes.documentation"
|
||||
)}</a
|
||||
>`
|
||||
"ui.panel.config.script.editor.sequence_sentence"
|
||||
)}
|
||||
</p>
|
||||
<paper-dropdown-menu-light
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.modes.label"
|
||||
)}
|
||||
no-animations
|
||||
<a
|
||||
href="https://home-assistant.io/docs/scripts/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._config.mode
|
||||
? MODES.indexOf(this._config.mode)
|
||||
: 0}
|
||||
@iron-select=${this._modeChanged}
|
||||
>
|
||||
${MODES.map(
|
||||
(mode) => html`
|
||||
<paper-item .mode=${mode}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.script.editor.modes.${mode}`
|
||||
) || mode}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
${this._config.mode &&
|
||||
MODES_MAX.includes(this._config.mode)
|
||||
? html` <paper-input
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.script.editor.max.${this._config.mode}`
|
||||
)}
|
||||
type="number"
|
||||
name="max"
|
||||
.value=${this._config.max || "10"}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>`
|
||||
: html``}
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.sequence"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.sequence_sentence"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/scripts/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.link_available_actions"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-action
|
||||
.actions=${this._config.sequence}
|
||||
@value-changed=${this._sequenceChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>
|
||||
`
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.link_available_actions"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-action
|
||||
.actions=${this._config.sequence}
|
||||
@value-changed=${this._sequenceChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
@@ -300,6 +395,33 @@ export class HaScriptEditor extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _runScript(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
await triggerScript(this.hass, this.scriptEntityId);
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.notification_toast.triggered",
|
||||
"name",
|
||||
this._config!.alias
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._switchYamlMode();
|
||||
break;
|
||||
case 1:
|
||||
this._deleteConfirm();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _switchYamlMode() {
|
||||
this._yamlMode = !this._yamlMode;
|
||||
}
|
||||
|
||||
private _modeChanged(ev: CustomEvent) {
|
||||
const mode = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||
?.mode;
|
||||
@@ -339,6 +461,15 @@ export class HaScriptEditor extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _onYamlChange(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
this._config = ev.detail.value;
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.target as any;
|
||||
|
@@ -12,7 +12,7 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { isServiceLoaded } from "../../../common/config/is_service_loaded";
|
||||
import { componentsWithService } from "../../../common/config/components_with_service";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import { checkCoreConfig } from "../../../data/core";
|
||||
@@ -49,11 +49,10 @@ export class HaConfigServerControl extends LitElement {
|
||||
changedProperties.has("hass") &&
|
||||
(!oldHass || oldHass.config.components !== this.hass.config.components)
|
||||
) {
|
||||
this._reloadableDomains = this.hass.config.components.filter(
|
||||
(component) =>
|
||||
!component.includes(".") &&
|
||||
isServiceLoaded(this.hass, component, "reload")
|
||||
);
|
||||
this._reloadableDomains = componentsWithService(
|
||||
this.hass,
|
||||
"reload"
|
||||
).sort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,24 +202,23 @@ export class HaConfigServerControl extends LitElement {
|
||||
)}
|
||||
</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>`
|
||||
: ""
|
||||
${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>`
|
||||
)}
|
||||
</ha-card>
|
||||
`
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
@@ -11,7 +13,7 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { deleteUser, fetchUsers, updateUser, User } from "../../../data/user";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
@@ -19,8 +21,6 @@ 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: "25%",
|
||||
width: "30%",
|
||||
template: (groupIds) => html`
|
||||
${this.hass.localize(`groups.${groupIds[0]}`)}
|
||||
`,
|
||||
@@ -66,6 +66,7 @@ export class HaConfigUsers extends LitElement {
|
||||
"ui.panel.config.users.picker.headers.system"
|
||||
),
|
||||
type: "icon",
|
||||
width: "80px",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
template: (generated) => html`
|
||||
|
@@ -13,7 +13,10 @@ 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 { subscribeRenderTemplate } from "../../../data/ws-templates";
|
||||
import {
|
||||
RenderTemplateResult,
|
||||
subscribeRenderTemplate,
|
||||
} from "../../../data/ws-templates";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
@@ -31,10 +34,9 @@ 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 3 entity values:
|
||||
For loop example getting entity values in the weather domain:
|
||||
|
||||
{% for states in states | slice(3) -%}
|
||||
{% set state = states | first %}
|
||||
{% for state in states.weather -%}
|
||||
{%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}
|
||||
{{ state.name | lower }} is {{state.state_with_unit}}
|
||||
{%- endfor %}.`;
|
||||
@@ -45,11 +47,11 @@ class HaPanelDevTemplate extends LitElement {
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@internalProperty() private _error = false;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _rendering = false;
|
||||
|
||||
@internalProperty() private _processed = "";
|
||||
@internalProperty() private _templateResult?: RenderTemplateResult;
|
||||
|
||||
@internalProperty() private _unsubRenderTemplate?: Promise<UnsubscribeFunc>;
|
||||
|
||||
@@ -140,9 +142,65 @@ class HaPanelDevTemplate extends LitElement {
|
||||
.active=${this._rendering}
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
<pre class="rendered ${classMap({ error: this._error })}">
|
||||
${this._processed}</pre
|
||||
>
|
||||
|
||||
<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>`}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -190,6 +248,12 @@ ${this._processed}</pre
|
||||
@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 {
|
||||
@@ -211,7 +275,7 @@ ${this._processed}</pre
|
||||
private _templateChanged(ev) {
|
||||
this._template = ev.detail.value;
|
||||
if (this._error) {
|
||||
this._error = false;
|
||||
this._error = undefined;
|
||||
}
|
||||
this._debounceRender();
|
||||
}
|
||||
@@ -223,7 +287,8 @@ ${this._processed}</pre
|
||||
this._unsubRenderTemplate = subscribeRenderTemplate(
|
||||
this.hass.connection,
|
||||
(result) => {
|
||||
this._processed = result;
|
||||
this._templateResult = result;
|
||||
this._error = undefined;
|
||||
},
|
||||
{
|
||||
template: this._template,
|
||||
@@ -231,9 +296,10 @@ ${this._processed}</pre
|
||||
);
|
||||
await this._unsubRenderTemplate;
|
||||
} catch (err) {
|
||||
this._error = true;
|
||||
this._error = "Unknown error";
|
||||
if (err.message) {
|
||||
this._processed = err.message;
|
||||
this._error = err.message;
|
||||
this._templateResult = undefined;
|
||||
}
|
||||
this._unsubRenderTemplate = undefined;
|
||||
} finally {
|
||||
|
@@ -79,10 +79,12 @@ class HaPanelHistory extends LitElement {
|
||||
></ha-date-range-picker>
|
||||
</div>
|
||||
${this._isLoading
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>`
|
||||
? html`<div class="progress-wrapper">
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: html`
|
||||
<state-history-charts
|
||||
.hass=${this.hass}
|
||||
@@ -196,6 +198,19 @@ 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%;
|
||||
|
@@ -21,7 +21,6 @@ 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")
|
||||
@@ -38,6 +37,9 @@ 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;
|
||||
|
||||
@@ -67,14 +69,14 @@ class HaLogbook extends LitElement {
|
||||
if (!this.entries?.length) {
|
||||
return html`
|
||||
<div class="container no-entries" .dir=${emitRTLDirection(this._rtl)}>
|
||||
${this.hass.localize("ui.panel.logbook.entries_not_found")}
|
||||
${this.hass.localize("ui.components.logbook.entries_not_found")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="container ha-scrollbar ${classMap({
|
||||
class="container ${classMap({
|
||||
narrow: this.narrow,
|
||||
rtl: this._rtl,
|
||||
"no-name": this.noName,
|
||||
@@ -82,11 +84,15 @@ class HaLogbook extends LitElement {
|
||||
})}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
${scroll({
|
||||
items: this.entries,
|
||||
renderItem: (item: LogbookEntry, index?: number) =>
|
||||
this._renderLogbookItem(item, index),
|
||||
})}
|
||||
${this.virtualize
|
||||
? scroll({
|
||||
items: this.entries,
|
||||
renderItem: (item: LogbookEntry, index?: number) =>
|
||||
this._renderLogbookItem(item, index),
|
||||
})
|
||||
: this.entries.map((item, index) =>
|
||||
this._renderLogbookItem(item, index)
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -143,20 +149,23 @@ class HaLogbook extends LitElement {
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
<span class="item-message">${item.message}</span>
|
||||
<span>${item_username ? ` (${item_username})` : ``}</span>
|
||||
${!item.context_event_type
|
||||
${item.message}
|
||||
${item_username
|
||||
? ` by ${item_username}`
|
||||
: !item.context_event_type
|
||||
? ""
|
||||
: item.context_event_type === "call_service"
|
||||
? // Service Call
|
||||
html` by service
|
||||
` by service
|
||||
${item.context_domain}.${item.context_service}`
|
||||
: item.context_entity_id === item.entity_id
|
||||
? // HomeKit or something that self references
|
||||
html` by
|
||||
${item.context_name
|
||||
? item.context_name
|
||||
: item.context_event_type}`
|
||||
` by
|
||||
${
|
||||
item.context_name
|
||||
? item.context_name
|
||||
: item.context_event_type
|
||||
}`
|
||||
: // Another entity such as an automation or script
|
||||
html` by
|
||||
<a
|
||||
@@ -185,106 +194,104 @@ class HaLogbook extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
static get styles(): CSSResult {
|
||||
return 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: 70px;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.time {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 75px;
|
||||
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;
|
||||
}
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
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 .item-message {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.no-name .message:first-letter {
|
||||
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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 "../../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-menu-button";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import "./ha-logbook";
|
||||
import {
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { fetchUsers } from "../../data/user";
|
||||
import { fetchPersons } from "../../data/person";
|
||||
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 {
|
||||
clearLogbookCache,
|
||||
getLogbookData,
|
||||
LogbookEntry,
|
||||
} from "../../data/logbook";
|
||||
import { mdiRefresh } from "@mdi/js";
|
||||
import "../../components/ha-date-range-picker";
|
||||
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
||||
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";
|
||||
|
||||
@customElement("ha-panel-logbook")
|
||||
export class HaPanelLogbook extends LitElement {
|
||||
@@ -125,6 +125,7 @@ export class HaPanelLogbook extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.entries=${this._entries}
|
||||
.userIdToName=${this._userIdToName}
|
||||
virtualize
|
||||
></ha-logbook>`}
|
||||
</ha-app-layout>
|
||||
`;
|
||||
|
@@ -21,6 +21,7 @@ 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";
|
||||
@@ -36,7 +37,6 @@ 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,11 +63,6 @@ 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] || "",
|
||||
};
|
||||
}
|
||||
@@ -92,29 +87,18 @@ 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 {
|
||||
|
@@ -76,11 +76,11 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
private _resizeObserver?: ResizeObserver;
|
||||
|
||||
public setConfig(config: CalendarCardConfig): void {
|
||||
if (!config.entities) {
|
||||
if (!config.entities?.length) {
|
||||
throw new Error("Entities must be defined");
|
||||
}
|
||||
|
||||
if (config.entities && !Array.isArray(config.entities)) {
|
||||
if (!Array.isArray(config.entities)) {
|
||||
throw new Error("Entities need to be an array");
|
||||
}
|
||||
|
||||
|
@@ -50,7 +50,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
["light", "switch", "sensor"]
|
||||
);
|
||||
|
||||
return { type: "entities", title: "My Title", entities: foundEntities };
|
||||
return { type: "entities", entities: foundEntities };
|
||||
}
|
||||
|
||||
@internalProperty() private _config?: EntitiesCardConfig;
|
||||
|
@@ -43,8 +43,8 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
background-color: #ef5350;
|
||||
color: white;
|
||||
background-color: var(--error-color);
|
||||
color: var(--color-on-error, white);
|
||||
padding: 8px;
|
||||
font-weight: 500;
|
||||
user-select: text;
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -20,18 +20,22 @@ import "../../../components/entity/state-badge";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import {
|
||||
ActionHandlerEvent,
|
||||
CallServiceActionConfig,
|
||||
MoreInfoActionConfig,
|
||||
} 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 {
|
||||
@@ -86,7 +90,14 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
state_color: true,
|
||||
...config,
|
||||
};
|
||||
const entities = processConfigEntities<GlanceConfigEntity>(config.entities);
|
||||
const entities = processConfigEntities<GlanceConfigEntity>(
|
||||
config.entities
|
||||
).map((entityConf) => {
|
||||
return {
|
||||
hold_action: { action: "more-info" } as MoreInfoActionConfig,
|
||||
...entityConf,
|
||||
};
|
||||
});
|
||||
|
||||
for (const entity of entities) {
|
||||
if (
|
||||
@@ -95,7 +106,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
!entity.tap_action.service) ||
|
||||
(entity.hold_action &&
|
||||
entity.hold_action.action === "call-service" &&
|
||||
!entity.hold_action.service)
|
||||
!(entity.hold_action as CallServiceActionConfig).service)
|
||||
) {
|
||||
throw new Error(
|
||||
'Missing required property "service" when tap_action or hold_action is call-service'
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import "../../../components/ha-icon-button";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@thomasloven/round-slider";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -20,7 +20,8 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../../../common/entity/state_icon";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-card";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../data/light";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { HomeAssistant, LightEntity } from "../../../types";
|
||||
@@ -32,7 +33,6 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LightCardConfig } from "./types";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
|
||||
@customElement("hui-light-card")
|
||||
export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
@@ -77,8 +77,9 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
this._config = {
|
||||
...config,
|
||||
tap_action: { action: "toggle" },
|
||||
hold_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
SUPPORT_BRIGHTNESS
|
||||
),
|
||||
"state-on": stateObj.state === "on",
|
||||
"state-unavailable": stateObj.state === "unavailable",
|
||||
"state-unavailable": stateObj.state === UNAVAILABLE,
|
||||
})}"
|
||||
.icon=${this._config.icon || stateIcon(stateObj)}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
|
@@ -14,7 +14,10 @@ import { classMap } from "lit-html/directives/class-map";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-markdown";
|
||||
import { subscribeRenderTemplate } from "../../../data/ws-templates";
|
||||
import {
|
||||
subscribeRenderTemplate,
|
||||
RenderTemplateResult,
|
||||
} from "../../../data/ws-templates";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type { MarkdownCardConfig } from "./types";
|
||||
@@ -40,7 +43,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@internalProperty() private _config?: MarkdownCardConfig;
|
||||
|
||||
@internalProperty() private _content = "";
|
||||
@internalProperty() private _templateResult?: RenderTemplateResult;
|
||||
|
||||
@internalProperty() private _unsubRenderTemplate?: Promise<UnsubscribeFunc>;
|
||||
|
||||
@@ -85,7 +88,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
class=${classMap({
|
||||
"no-header": !this._config.title,
|
||||
})}
|
||||
.content="${this._content}"
|
||||
.content="${this._templateResult?.result}"
|
||||
></ha-markdown>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -127,7 +130,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
this._unsubRenderTemplate = subscribeRenderTemplate(
|
||||
this.hass.connection,
|
||||
(result) => {
|
||||
this._content = result;
|
||||
this._templateResult = result;
|
||||
},
|
||||
{
|
||||
template: this._config.content,
|
||||
@@ -139,7 +142,10 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
);
|
||||
} catch (_err) {
|
||||
this._content = this._config!.content;
|
||||
this._templateResult = {
|
||||
result: this._config!.content,
|
||||
listeners: { all: false, domains: [], entities: [] },
|
||||
};
|
||||
this._unsubRenderTemplate = undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -28,10 +28,10 @@ import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import "../components/hui-image";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { PictureGlanceCardConfig, PictureGlanceEntityConfig } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
const STATES_OFF = new Set(["closed", "locked", "not_home", "off"]);
|
||||
|
||||
@@ -104,7 +104,10 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
});
|
||||
|
||||
this._config = config;
|
||||
this._config = {
|
||||
hold_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
@@ -225,6 +228,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
|
||||
entityConf = {
|
||||
tap_action: { action: dialog ? "more-info" : "toggle" },
|
||||
hold_action: { action: "more-info" },
|
||||
...entityConf,
|
||||
};
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import "../../../components/ha-icon-button";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@thomasloven/round-slider";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
query,
|
||||
svg,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { UNIT_F } from "../../../common/const";
|
||||
@@ -20,6 +20,8 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/ha-card";
|
||||
import type { HaCard } from "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import {
|
||||
ClimateEntity,
|
||||
CLIMATE_PRESET_NONE,
|
||||
@@ -28,14 +30,11 @@ import {
|
||||
} from "../../../data/climate";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { ThermostatCardConfig } from "./types";
|
||||
import type { HaCard } from "../../../components/ha-card";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
|
||||
const modeIcons: { [mode in HvacMode]: string } = {
|
||||
auto: "hass:calendar-sync",
|
||||
@@ -385,8 +384,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
class="${classMap({ "selected-icon": currentMode === mode })}"
|
||||
.mode="${mode}"
|
||||
.icon="${modeIcons[mode]}"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler()}
|
||||
@click=${this._handleAction}
|
||||
tabindex="0"
|
||||
></ha-icon-button>
|
||||
`;
|
||||
|
@@ -154,6 +154,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
if (["touchend", "touchcancel"].includes(ev.type) && this.cancelled) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target as HTMLElement;
|
||||
// Prevent mouse event if touch event
|
||||
if (ev.cancelable) {
|
||||
ev.preventDefault();
|
||||
@@ -164,7 +165,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
this.timer = undefined;
|
||||
}
|
||||
if (options.hasHold && this.held) {
|
||||
fireEvent(element, "action", { action: "hold" });
|
||||
fireEvent(target, "action", { action: "hold" });
|
||||
} else if (options.hasDoubleClick) {
|
||||
if (
|
||||
(ev.type === "click" && (ev as MouseEvent).detail < 2) ||
|
||||
@@ -172,15 +173,15 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
) {
|
||||
this.dblClickTimeout = window.setTimeout(() => {
|
||||
this.dblClickTimeout = undefined;
|
||||
fireEvent(element, "action", { action: "tap" });
|
||||
fireEvent(target, "action", { action: "tap" });
|
||||
}, 250);
|
||||
} else {
|
||||
clearTimeout(this.dblClickTimeout);
|
||||
this.dblClickTimeout = undefined;
|
||||
fireEvent(element, "action", { action: "double_tap" });
|
||||
fireEvent(target, "action", { action: "double_tap" });
|
||||
}
|
||||
} else {
|
||||
fireEvent(element, "action", { action: "tap" });
|
||||
fireEvent(target, "action", { action: "tap" });
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -63,6 +63,8 @@ const HIDE_DOMAIN = new Set([
|
||||
"zone",
|
||||
]);
|
||||
|
||||
const HIDE_PLATFORM = new Set(["mobile_app"]);
|
||||
|
||||
let subscribedRegistries = false;
|
||||
|
||||
interface SplittedByAreas {
|
||||
@@ -206,11 +208,23 @@ export const computeCards = (
|
||||
return cards;
|
||||
};
|
||||
|
||||
const computeDefaultViewStates = (entities: HassEntities): HassEntities => {
|
||||
const computeDefaultViewStates = (
|
||||
entities: HassEntities,
|
||||
entityEntries: EntityRegistryEntry[]
|
||||
): HassEntities => {
|
||||
const states = {};
|
||||
const hiddenEntities = new Set(
|
||||
entityEntries
|
||||
.filter((entry) => HIDE_PLATFORM.has(entry.platform))
|
||||
.map((entry) => entry.entity_id)
|
||||
);
|
||||
|
||||
Object.keys(entities).forEach((entityId) => {
|
||||
const stateObj = entities[entityId];
|
||||
if (!HIDE_DOMAIN.has(computeStateDomain(stateObj))) {
|
||||
if (
|
||||
!HIDE_DOMAIN.has(computeStateDomain(stateObj)) &&
|
||||
!hiddenEntities.has(stateObj.entity_id)
|
||||
) {
|
||||
states[entityId] = entities[entityId];
|
||||
}
|
||||
});
|
||||
@@ -317,7 +331,7 @@ export const generateDefaultViewConfig = (
|
||||
entities: HassEntities,
|
||||
localize: LocalizeFunc
|
||||
): LovelaceViewConfig => {
|
||||
const states = computeDefaultViewStates(entities);
|
||||
const states = computeDefaultViewStates(entities, entityEntries);
|
||||
const path = "default_view";
|
||||
const title = "Home";
|
||||
const icon = undefined;
|
||||
|
@@ -30,12 +30,6 @@ export const loadLovelaceResources = (
|
||||
loadModule(normalizedUrl);
|
||||
break;
|
||||
|
||||
case "html":
|
||||
import(
|
||||
/* webpackChunkName: "import-href-polyfill" */ "../../../resources/html-import/import-href"
|
||||
).then(({ importHref }) => importHref(normalizedUrl));
|
||||
break;
|
||||
|
||||
default:
|
||||
// eslint-disable-next-line
|
||||
console.warn(`Unknown resource type specified: ${resource.type}`);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
export interface Condition {
|
||||
entity: string;
|
||||
@@ -13,7 +14,7 @@ export function checkConditionsMet(
|
||||
return conditions.every((c) => {
|
||||
const state = hass.states[c.entity]
|
||||
? hass!.states[c.entity].state
|
||||
: "unavailable";
|
||||
: UNAVAILABLE;
|
||||
|
||||
return c.state ? state === c.state : state !== c.state_not;
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-service-picker";
|
||||
import {
|
||||
ActionConfig,
|
||||
@@ -20,17 +21,6 @@ import {
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EditorTarget } from "../editor/types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"action-changed": undefined;
|
||||
}
|
||||
// for add event listener
|
||||
interface HTMLElementEventMap {
|
||||
"action-changed": HASSDomEvent<undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hui-action-editor")
|
||||
export class HuiActionEditor extends LitElement {
|
||||
@property() public config?: ActionConfig;
|
||||
@@ -42,21 +32,21 @@ export class HuiActionEditor extends LitElement {
|
||||
@property() protected hass?: HomeAssistant;
|
||||
|
||||
get _action(): string {
|
||||
return this.config!.action || "";
|
||||
return this.config?.action || "";
|
||||
}
|
||||
|
||||
get _navigation_path(): string {
|
||||
const config = this.config! as NavigateActionConfig;
|
||||
const config = this.config as NavigateActionConfig;
|
||||
return config.navigation_path || "";
|
||||
}
|
||||
|
||||
get _url_path(): string {
|
||||
const config = this.config! as UrlActionConfig;
|
||||
const config = this.config as UrlActionConfig;
|
||||
return config.url_path || "";
|
||||
}
|
||||
|
||||
get _service(): string {
|
||||
const config = this.config! as CallServiceActionConfig;
|
||||
const config = this.config as CallServiceActionConfig;
|
||||
return config.service || "";
|
||||
}
|
||||
|
||||
@@ -107,13 +97,14 @@ export class HuiActionEditor extends LitElement {
|
||||
.configValue="${"service"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></ha-service-picker>
|
||||
<h3>Toggle Editor to input Service Data</h3>
|
||||
<b>Service data can only be entered in the code editor</b>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event): void {
|
||||
ev.stopPropagation();
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
@@ -121,12 +112,12 @@ export class HuiActionEditor extends LitElement {
|
||||
if (this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue === "action") {
|
||||
this.config = { action: "none" };
|
||||
}
|
||||
if (target.configValue) {
|
||||
this.config = { ...this.config!, [target.configValue!]: target.value };
|
||||
fireEvent(this, "action-changed");
|
||||
const newConfig =
|
||||
target.configValue === "action"
|
||||
? { action: target.value }
|
||||
: { ...this.config!, [target.configValue!]: target.value };
|
||||
fireEvent(this, "value-changed", { value: newConfig });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,10 +18,10 @@ import Sortable, {
|
||||
} from "sortablejs/modular/sortable.core.esm";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { sortableStyles } from "../../../resources/ha-sortable-style";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EditorTarget } from "../editor/types";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
|
||||
@customElement("hui-entity-editor")
|
||||
@@ -34,7 +34,9 @@ export class HuiEntityEditor extends LitElement {
|
||||
|
||||
@internalProperty() private _attached = false;
|
||||
|
||||
private _sortable?;
|
||||
@internalProperty() private _renderEmptySortable = false;
|
||||
|
||||
private _sortable?: Sortable;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
@@ -60,26 +62,28 @@ export class HuiEntityEditor extends LitElement {
|
||||
")"}
|
||||
</h3>
|
||||
<div class="entities">
|
||||
${guard([this.entities], () =>
|
||||
this.entities!.map((entityConf, index) => {
|
||||
return html`
|
||||
<div class="entity" data-entity-id=${entityConf.entity}>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${entityConf.entity}
|
||||
.index=${index}
|
||||
@change=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
${guard([this.entities, this._renderEmptySortable], () =>
|
||||
this._renderEmptySortable
|
||||
? ""
|
||||
: this.entities!.map((entityConf, index) => {
|
||||
return html`
|
||||
<div class="entity" data-entity-id=${entityConf.entity}>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${entityConf.entity}
|
||||
.index=${index}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
@change=${this._addEntity}
|
||||
@value-changed=${this._addEntity}
|
||||
></ha-entity-picker>
|
||||
`;
|
||||
}
|
||||
@@ -112,10 +116,16 @@ export class HuiEntityEditor extends LitElement {
|
||||
}
|
||||
|
||||
if (entitiesChanged) {
|
||||
this._sortable.sort(this.entities?.map((entity) => entity.entity));
|
||||
this._handleEntitiesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleEntitiesChanged() {
|
||||
this._renderEmptySortable = true;
|
||||
await this.updateComplete;
|
||||
this._renderEmptySortable = false;
|
||||
}
|
||||
|
||||
private _createSortable() {
|
||||
this._sortable = new Sortable(this.shadowRoot!.querySelector(".entities"), {
|
||||
animation: 150,
|
||||
@@ -126,15 +136,15 @@ export class HuiEntityEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _addEntity(ev: Event): Promise<void> {
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (target.value === "") {
|
||||
private async _addEntity(ev: CustomEvent): Promise<void> {
|
||||
const value = ev.detail.value;
|
||||
if (value === "") {
|
||||
return;
|
||||
}
|
||||
const newConfigEntities = this.entities!.concat({
|
||||
entity: target.value as string,
|
||||
entity: value as string,
|
||||
});
|
||||
target.value = "";
|
||||
(ev.target as HaEntityPicker).value = "";
|
||||
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
||||
}
|
||||
|
||||
@@ -150,16 +160,17 @@ export class HuiEntityEditor extends LitElement {
|
||||
fireEvent(this, "entities-changed", { entities: newEntities });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event): void {
|
||||
const target = ev.target! as EditorTarget;
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const value = ev.detail.value;
|
||||
const index = (ev.target as any).index;
|
||||
const newConfigEntities = this.entities!.concat();
|
||||
|
||||
if (target.value === "") {
|
||||
newConfigEntities.splice(target.index!, 1);
|
||||
if (value === "") {
|
||||
newConfigEntities.splice(index, 1);
|
||||
} else {
|
||||
newConfigEntities[target.index!] = {
|
||||
...newConfigEntities[target.index!],
|
||||
entity: target.value!,
|
||||
newConfigEntities[index] = {
|
||||
...newConfigEntities[index],
|
||||
entity: value!,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
@@ -16,6 +16,7 @@ import { STATES_OFF } from "../../../common/const";
|
||||
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
|
||||
import "../../../components/ha-camera-stream";
|
||||
import { fetchThumbnailUrlWithCache } from "../../../data/camera";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { CameraEntity, HomeAssistant } from "../../../types";
|
||||
|
||||
const UPDATE_INTERVAL = 10000;
|
||||
@@ -73,7 +74,7 @@ export class HuiImage extends LitElement {
|
||||
}
|
||||
const ratio = this.aspectRatio ? parseAspectRatio(this.aspectRatio) : null;
|
||||
const stateObj = this.entity ? this.hass.states[this.entity] : undefined;
|
||||
const state = stateObj ? stateObj.state : "unavailable";
|
||||
const state = stateObj ? stateObj.state : UNAVAILABLE;
|
||||
|
||||
// Figure out image source to use
|
||||
let imageSrc: string | undefined;
|
||||
@@ -131,8 +132,9 @@ export class HuiImage extends LitElement {
|
||||
${this.cameraImage && this.cameraView === "live"
|
||||
? html`
|
||||
<ha-camera-stream
|
||||
muted
|
||||
.hass=${this.hass}
|
||||
.stateObj="${cameraObj}"
|
||||
.stateObj=${cameraObj}
|
||||
></ha-camera-stream>
|
||||
`
|
||||
: html`
|
||||
|
@@ -5,14 +5,16 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeRTL } from "../../../../common/util/compute_rtl";
|
||||
import { deepEqual } from "../../../../common/util/deep-equal";
|
||||
import "../../../../components/ha-circular-progress";
|
||||
import "../../../../components/ha-code-editor";
|
||||
import type { HaCodeEditor } from "../../../../components/ha-code-editor";
|
||||
import type {
|
||||
@@ -20,14 +22,12 @@ import type {
|
||||
LovelaceConfig,
|
||||
} from "../../../../data/lovelace";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { handleStructError } from "../../common/structs/handle-errors";
|
||||
import { getCardElementClass } from "../../create-element/create-card-element";
|
||||
import type { EntityConfig } from "../../entity-rows/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import type { GUIModeChangedEvent } from "../types";
|
||||
import "../../../../components/ha-circular-progress";
|
||||
import { deepEqual } from "../../../../common/util/deep-equal";
|
||||
import { handleStructError } from "../../common/structs/handle-errors";
|
||||
import { GUISupportError } from "../gui-support-error";
|
||||
import type { GUIModeChangedEvent } from "../types";
|
||||
|
||||
export interface ConfigChangedEvent {
|
||||
config: LovelaceCardConfig;
|
||||
@@ -78,6 +78,9 @@ export class HuiCardEditor extends LitElement {
|
||||
@query("ha-code-editor") _yamlEditor?: HaCodeEditor;
|
||||
|
||||
public get yaml(): string {
|
||||
if (!this._yaml) {
|
||||
this._yaml = safeDump(this._config);
|
||||
}
|
||||
return this._yaml || "";
|
||||
}
|
||||
|
||||
@@ -101,7 +104,7 @@ export class HuiCardEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
this._config = config;
|
||||
this._yaml = safeDump(config);
|
||||
this._yaml = undefined;
|
||||
this._error = undefined;
|
||||
this._setConfig();
|
||||
}
|
||||
|
@@ -2,14 +2,18 @@ import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { assert, boolean, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stateIcon } from "../../../../common/entity/state_icon";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import "../../../../components/ha-switch";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { ButtonCardConfig } from "../../cards/types";
|
||||
@@ -17,16 +21,8 @@ import "../../components/hui-action-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-formfield";
|
||||
import { actionConfigStruct, EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import { assert, object, string, optional, boolean } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -63,11 +59,11 @@ export class HuiButtonCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _show_name(): boolean {
|
||||
return this._config!.show_name || true;
|
||||
return this._config!.show_name ?? true;
|
||||
}
|
||||
|
||||
get _show_state(): boolean {
|
||||
return this._config!.show_state || false;
|
||||
return this._config!.show_state ?? false;
|
||||
}
|
||||
|
||||
get _icon(): string {
|
||||
@@ -75,7 +71,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _show_icon(): boolean {
|
||||
return this._config!.show_icon || true;
|
||||
return this._config!.show_icon ?? true;
|
||||
}
|
||||
|
||||
get _icon_height(): string {
|
||||
@@ -85,11 +81,11 @@ export class HuiButtonCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _tap_action(): ActionConfig {
|
||||
return this._config!.tap_action || { action: "more-info" };
|
||||
return this._config!.tap_action || { action: "toggle" };
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "none" };
|
||||
return this._config!.hold_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
@@ -123,7 +119,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<div class="side-by-side">
|
||||
@@ -161,7 +157,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked="${this._show_name !== false}"
|
||||
.configValue="${"show_name"}"
|
||||
@change="${this._valueChanged}"
|
||||
@change="${this._change}"
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@@ -175,7 +171,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked=${this._show_state !== false}
|
||||
.configValue=${"show_state"}
|
||||
@change=${this._valueChanged}
|
||||
@change=${this._change}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@@ -189,7 +185,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked="${this._show_icon !== false}"
|
||||
.configValue="${"show_icon"}"
|
||||
@change="${this._valueChanged}"
|
||||
@change="${this._change}"
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@@ -225,7 +221,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
.label="${this.hass.localize(
|
||||
@@ -237,27 +233,43 @@ export class HuiButtonCardEditor extends LitElement
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _change(ev: Event) {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = target.checked;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
@@ -266,18 +278,11 @@ export class HuiButtonCardEditor extends LitElement
|
||||
target.configValue === "icon_height" &&
|
||||
!isNaN(Number(target.value))
|
||||
) {
|
||||
newValue = `${String(target.value)}px`;
|
||||
newValue = `${String(value)}px`;
|
||||
}
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]:
|
||||
target.checked !== undefined
|
||||
? target.checked
|
||||
: newValue !== undefined
|
||||
? newValue
|
||||
: target.value
|
||||
? target.value
|
||||
: target.config,
|
||||
[target.configValue!]: newValue !== undefined ? newValue : value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -2,11 +2,12 @@ import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stateIcon } from "../../../../common/entity/state_icon";
|
||||
import "../../../../components/ha-icon-input";
|
||||
@@ -17,13 +18,8 @@ import "../../components/hui-action-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import { actionConfigStruct, EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { string, object, optional, assert } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -66,11 +62,11 @@ export class HuiLightCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "none" };
|
||||
return this._config!.hold_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _double_tap_action(): ActionConfig {
|
||||
return this._config!.double_tap_action || { action: "none" };
|
||||
get _double_tap_action(): ActionConfig | undefined {
|
||||
return this._config!.double_tap_action;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -100,7 +96,7 @@ export class HuiLightCardEditor extends LitElement
|
||||
.value=${this._entity}
|
||||
.configValue=${"entity"}
|
||||
.includeDomains=${includeDomains}
|
||||
@change=${this._valueChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<div class="side-by-side">
|
||||
@@ -145,7 +141,7 @@ export class HuiLightCardEditor extends LitElement
|
||||
.config=${this._hold_action}
|
||||
.actions=${actions}
|
||||
.configValue=${"hold_action"}
|
||||
@action-changed=${this._valueChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-action-editor>
|
||||
|
||||
<hui-action-editor
|
||||
@@ -158,32 +154,30 @@ export class HuiLightCardEditor extends LitElement
|
||||
.config=${this._double_tap_action}
|
||||
.actions=${actions}
|
||||
.configValue=${"double_tap_action"}
|
||||
@action-changed=${this._valueChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-action-editor>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: target.value ? target.value : target.config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -2,11 +2,12 @@ import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -14,13 +15,8 @@ import { PictureCardConfig } from "../../cards/types";
|
||||
import "../../components/hui-action-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import { actionConfigStruct, EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { string, object, optional, assert } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -89,7 +85,7 @@ export class HuiPictureCardEditor extends LitElement
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
.label="${this.hass.localize(
|
||||
@@ -101,7 +97,7 @@ export class HuiPictureCardEditor extends LitElement
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
@@ -114,26 +110,24 @@ export class HuiPictureCardEditor extends LitElement
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: target.value ? target.value : target.config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -5,14 +5,16 @@ import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { assert, boolean, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-switch";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-switch";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { PictureEntityCardConfig } from "../../cards/types";
|
||||
@@ -20,14 +22,8 @@ import "../../components/hui-action-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import { actionConfigStruct, EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import { assert, object, string, optional, boolean } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -86,16 +82,16 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
return this._config!.tap_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "more-info" };
|
||||
get _hold_action(): ActionConfig | undefined {
|
||||
return this._config!.hold_action;
|
||||
}
|
||||
|
||||
get _show_name(): boolean {
|
||||
return this._config!.show_name || true;
|
||||
return this._config!.show_name ?? true;
|
||||
}
|
||||
|
||||
get _show_state(): boolean {
|
||||
return this._config!.show_state || true;
|
||||
return this._config!.show_state ?? true;
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
@@ -123,7 +119,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<paper-input
|
||||
@@ -155,7 +151,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._camera_image}"
|
||||
.configValue=${"camera_image"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.includeDomains=${includeDomains}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
@@ -184,8 +180,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
type="number"
|
||||
.value="${Number(this._aspect_ratio.replace("%", ""))}"
|
||||
.value="${this._aspect_ratio}"
|
||||
.configValue="${"aspect_ratio"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
@@ -201,7 +196,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked="${this._config!.show_name !== false}"
|
||||
.configValue="${"show_name"}"
|
||||
@change="${this._valueChanged}"
|
||||
@change="${this._change}"
|
||||
></ha-switch
|
||||
></ha-formfield>
|
||||
</div>
|
||||
@@ -215,7 +210,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
<ha-switch
|
||||
.checked="${this._config!.show_state !== false}"
|
||||
.configValue="${"show_state"}"
|
||||
@change="${this._valueChanged}"
|
||||
@change="${this._change}"
|
||||
></ha-switch
|
||||
></ha-formfield>
|
||||
</div>
|
||||
@@ -231,7 +226,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
.label="${this.hass.localize(
|
||||
@@ -243,7 +238,7 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
@@ -256,34 +251,43 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _change(ev: Event) {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
let value = target.value;
|
||||
const value = target.checked;
|
||||
|
||||
if (target.configValue! === "aspect_ratio" && target.value) {
|
||||
value += "%";
|
||||
}
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.configValue) {
|
||||
if (value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]:
|
||||
target.checked !== undefined
|
||||
? target.checked
|
||||
: value || target.config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -5,11 +5,12 @@ import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { array, assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
@@ -25,10 +26,8 @@ import {
|
||||
actionConfigStruct,
|
||||
EditorTarget,
|
||||
entitiesConfigStruct,
|
||||
EntitiesEditorEvent,
|
||||
} from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { assert, string, object, optional, array } from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -92,21 +91,13 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
}
|
||||
|
||||
get _tap_action(): ActionConfig {
|
||||
return this._config!.tap_action || { action: "more-info" };
|
||||
return this._config!.tap_action || { action: "toggle" };
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _show_name(): boolean {
|
||||
return this._config!.show_name || false;
|
||||
}
|
||||
|
||||
get _show_state(): boolean {
|
||||
return this._config!.show_state || false;
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "";
|
||||
}
|
||||
@@ -151,7 +142,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._camera_image}"
|
||||
.configValue=${"camera_image"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
.includeDomains=${includeDomains}
|
||||
></ha-entity-picker>
|
||||
@@ -180,8 +171,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
type="number"
|
||||
.value="${Number(this._aspect_ratio.replace("%", ""))}"
|
||||
.value="${this._aspect_ratio}"
|
||||
.configValue="${"aspect_ratio"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
@@ -195,7 +185,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
.hass=${this.hass}
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
@change="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<div class="side-by-side">
|
||||
@@ -209,7 +199,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
.label="${this.hass.localize(
|
||||
@@ -221,7 +211,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
</div>
|
||||
<hui-entity-editor
|
||||
@@ -239,36 +229,29 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
let value = target.value;
|
||||
|
||||
if (target.configValue! === "aspect_ratio" && target.value) {
|
||||
value += "%";
|
||||
}
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (ev.detail && ev.detail.entities) {
|
||||
this._config = { ...this._config, entities: ev.detail.entities };
|
||||
|
||||
this._configEntities = processEditorEntities(this._config.entities);
|
||||
} else if (target.configValue) {
|
||||
if (
|
||||
this[`_${target.configValue}`] === value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === "") {
|
||||
if (value !== false && !value) {
|
||||
this._config = { ...this._config };
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: value || target.config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import {
|
||||
ShowViewConfig,
|
||||
} from "../../../data/lovelace";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
import { optional, string, object, union } from "superstruct";
|
||||
import { optional, string, object, union, boolean } from "superstruct";
|
||||
import { EntityId } from "../common/structs/is-entity-id";
|
||||
import { Icon } from "../common/structs/is-icon";
|
||||
|
||||
@@ -81,6 +81,10 @@ export const entitiesConfigStruct = union([
|
||||
entity: EntityId,
|
||||
name: optional(string()),
|
||||
icon: optional(Icon),
|
||||
image: optional(string()),
|
||||
secondary_info: optional(string()),
|
||||
format: optional(string()),
|
||||
state_color: optional(boolean()),
|
||||
}),
|
||||
EntityId,
|
||||
]);
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -15,6 +15,7 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import { HaSwitch } from "../../../../components/ha-switch";
|
||||
import "../../../../components/user/ha-user-badge";
|
||||
import { LovelaceViewConfig, ShowViewConfig } from "../../../../data/lovelace";
|
||||
import { fetchUsers, User } from "../../../../data/user";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -69,14 +70,19 @@ export class HuiViewVisibilityEditor extends LitElement {
|
||||
</p>
|
||||
${this._sortedUsers(this._users).map(
|
||||
(user) => html`
|
||||
<paper-item>
|
||||
<paper-icon-item>
|
||||
<ha-user-badge
|
||||
slot="item-icon"
|
||||
.hass=${this.hass}
|
||||
.user=${user}
|
||||
></ha-user-badge>
|
||||
<paper-item-body>${user.name}</paper-item-body>
|
||||
<ha-switch
|
||||
.userId="${user.id}"
|
||||
@change=${this.valChange}
|
||||
.checked=${this.checkUser(user.id)}
|
||||
></ha-switch>
|
||||
</paper-item>
|
||||
</paper-icon-item>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
|
@@ -3,8 +3,8 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
@@ -28,7 +28,7 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Invalid Configuration: 'icon' required");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
@@ -29,12 +29,13 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Error in element configuration");
|
||||
}
|
||||
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.toggle(
|
||||
"clickable",
|
||||
config.tap_action && config.tap_action.action !== "none"
|
||||
this._config.tap_action && this._config.tap_action.action !== "none"
|
||||
);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -16,9 +16,9 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceElement, StateBadgeElementConfig } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
@customElement("hui-state-badge-element")
|
||||
export class HuiStateBadgeElement extends LitElement
|
||||
@@ -32,7 +32,7 @@ export class HuiStateBadgeElement extends LitElement
|
||||
throw Error("Invalid Configuration: 'entity' required");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -18,9 +18,9 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceElement, StateIconElementConfig } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
@customElement("hui-state-icon-element")
|
||||
export class HuiStateIconElement extends LitElement implements LovelaceElement {
|
||||
@@ -33,7 +33,11 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Invalid Configuration: 'entity' required");
|
||||
}
|
||||
|
||||
this._config = { state_color: true, ...config };
|
||||
this._config = {
|
||||
state_color: true,
|
||||
hold_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -18,9 +18,9 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
import { LovelaceElement, StateLabelElementConfig } from "./types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
|
||||
@customElement("hui-state-label-element")
|
||||
class HuiStateLabelElement extends LitElement implements LovelaceElement {
|
||||
@@ -33,7 +33,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Invalid Configuration: 'entity' required");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@@ -22,6 +23,19 @@ class HuiGroupEntityRow extends LitElement implements LovelaceRow {
|
||||
|
||||
@internalProperty() private _config?: EntityConfig;
|
||||
|
||||
private _computeCanToggle(hass: HomeAssistant, entityIds: string[]): boolean {
|
||||
return entityIds.some((entityId) => {
|
||||
const domain = computeDomain(entityId);
|
||||
if (domain === "group") {
|
||||
return this._computeCanToggle(
|
||||
hass,
|
||||
this.hass?.states[entityId].attributes["entity_id"]
|
||||
);
|
||||
}
|
||||
return DOMAINS_TOGGLE.has(domain);
|
||||
});
|
||||
}
|
||||
|
||||
public setConfig(config: EntityConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Configuration error");
|
||||
@@ -50,7 +64,7 @@ class HuiGroupEntityRow extends LitElement implements LovelaceRow {
|
||||
|
||||
return html`
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
${this._computeCanToggle(stateObj.attributes.entity_id)
|
||||
${this._computeCanToggle(this.hass, stateObj.attributes.entity_id)
|
||||
? html`
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
@@ -69,12 +83,6 @@ class HuiGroupEntityRow extends LitElement implements LovelaceRow {
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeCanToggle(entityIds): boolean {
|
||||
return entityIds.some((entityId) =>
|
||||
DOMAINS_TOGGLE.has(entityId.split(".", 1)[0])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -11,7 +11,7 @@ import "../../../components/ha-date-input";
|
||||
import type { HaDateInput } from "../../../components/ha-date-input";
|
||||
import "../../../components/paper-time-input";
|
||||
import type { PaperTimeInput } from "../../../components/paper-time-input";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||
import { setInputDateTimeValue } from "../../../data/input_datetime";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
@@ -70,10 +70,10 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
? html`
|
||||
<paper-time-input
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
.hour=${stateObj.state === "unknown"
|
||||
.hour=${stateObj.state === UNKNOWN
|
||||
? ""
|
||||
: ("0" + stateObj.attributes.hour).slice(-2)}
|
||||
.min=${stateObj.state === "unknown"
|
||||
.min=${stateObj.state === UNKNOWN
|
||||
? ""
|
||||
: ("0" + stateObj.attributes.minute).slice(-2)}
|
||||
.amPm=${false}
|
||||
|
@@ -22,6 +22,7 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
|
||||
interface SensorEntityConfig extends EntitiesCardEntityConfig {
|
||||
format?: "relative" | "date" | "time" | "datetime";
|
||||
@@ -71,8 +72,7 @@ class HuiSensorEntityRow extends LitElement implements LovelaceRow {
|
||||
>
|
||||
${stateObj.attributes.device_class ===
|
||||
SENSOR_DEVICE_CLASS_TIMESTAMP &&
|
||||
stateObj.state !== "unavailable" &&
|
||||
stateObj.state !== "unknown"
|
||||
!UNAVAILABLE_STATES.includes(stateObj.state)
|
||||
? html`
|
||||
<hui-timestamp-display
|
||||
.hass=${this.hass}
|
||||
|
@@ -2,9 +2,9 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -125,7 +125,9 @@ class HuiTimerEntityRow extends LitElement {
|
||||
}
|
||||
|
||||
if (stateObj.state === "idle" || this._timeRemaining === 0) {
|
||||
return this.hass!.localize("state.timer." + stateObj.state);
|
||||
return (
|
||||
this.hass!.localize(`state.timer.${stateObj.state}`) || stateObj.state
|
||||
);
|
||||
}
|
||||
|
||||
let display = secondsToDuration(this._timeRemaining || 0);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user