Compare commits

..

56 Commits

Author SHA1 Message Date
Bram Kragten
a70e6c49a1 Merge pull request #7940 from home-assistant/dev
20201210.0
2020-12-10 16:56:08 +01:00
Bram Kragten
3d83d5f4b5 Bumped version to 20201210.0 2020-12-10 15:57:13 +01:00
Philip Allgaier
f9dece0743 Add copy YAML (automation & script) fallback without navigator.clipboard (#7900) 2020-12-10 15:55:57 +01:00
Bram Kragten
ac0871d0e8 Use correct device name in target picker (#7939) 2020-12-10 15:12:33 +01:00
HomeAssistant Azure
ffc19e591d [ci skip] Translation update 2020-12-10 00:32:24 +00:00
HomeAssistant Azure
c53380ca3d [ci skip] Translation update 2020-12-09 00:32:38 +00:00
Bram Kragten
7c74a2026a Correct tabs helpers page (#7928)
Fixes https://github.com/home-assistant/frontend/issues/7926
2020-12-08 11:47:30 +01:00
HomeAssistant Azure
adaed438d9 [ci skip] Translation update 2020-12-08 00:32:29 +00:00
uvjustin
baf38305cb Remove use of named groups in regexp (#7921) 2020-12-07 15:18:27 +01:00
HomeAssistant Azure
8254712521 [ci skip] Translation update 2020-12-07 00:33:14 +00:00
HomeAssistant Azure
53214781e3 [ci skip] Translation update 2020-12-06 00:32:39 +00:00
HomeAssistant Azure
88cbbbdf65 [ci skip] Translation update 2020-12-05 00:33:30 +00:00
Bram Kragten
c10dca9c7b Merge pull request #7910 from home-assistant/dev 2020-12-04 21:46:54 +01:00
Bram Kragten
7f2ebb4bde Bumped version to 20201204.0 2020-12-04 21:33:37 +01:00
Bram Kragten
f1abb60e4a Add message when no matches for selectors, clear config when deleted (#7906) 2020-12-04 20:49:24 +01:00
Bram Kragten
e014c7aff6 Entity is not required for button card (#7909)
Fixes https://github.com/home-assistant/frontend/issues/7908
2020-12-04 12:02:58 -06:00
Bram Kragten
b79c03433e Don't update device picker while open (#7903) 2020-12-04 12:05:01 +01:00
HomeAssistant Azure
34eb4d974d [ci skip] Translation update 2020-12-04 00:32:26 +00:00
Zack Barett
3264be3c5e Move main function to events on hui-view for custom views to use (#7861) 2020-12-03 18:16:55 -06:00
Bram Kragten
655f4f75fb Change import blueprint fab icon (#7894) 2020-12-03 22:41:13 +01:00
Bram Kragten
4383f31696 Translation logic tweaks (#7901)
* Translation logic tweaks

* Comments
2020-12-03 22:29:52 +01:00
Paulus Schoutsen
99eb15d15e Improve blueprint translations (#7892) 2020-12-03 18:08:14 +01:00
Bram Kragten
2682c6e150 Merge pull request #7891 from home-assistant/dev 2020-12-03 17:22:08 +01:00
Bram Kragten
3a5d854e6d Bumped version to 20201203.0 2020-12-03 17:00:56 +01:00
Bram Kragten
1e90c6387c More BP tweaks (#7884) 2020-12-03 09:59:43 -06:00
Philip Allgaier
2cca25f4d0 Navigate back to area overview after area deletion (#7890) 2020-12-03 16:52:44 +01:00
Bram Kragten
565724d201 Fix translation race condition? (#7885) 2020-12-03 16:52:09 +01:00
Bram Kragten
3e4955becd UI tweaks for BP (#7883)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-12-03 14:18:17 +01:00
Philip Allgaier
7b560c727f Adjust text since entities can now be in areas too (#7880) 2020-12-03 14:05:16 +01:00
HomeAssistant Azure
35abd9dfdb [ci skip] Translation update 2020-12-03 00:32:38 +00:00
Bram Kragten
b7ccf3e0e5 Merge pull request #7875 from home-assistant/dev 2020-12-02 19:51:36 +01:00
Bram Kragten
0d9ab8fdd0 Bumped version to 20201202.0 2020-12-02 19:31:43 +01:00
Bram Kragten
303f9290a8 Add more device disabled ui (#7874) 2020-12-02 19:31:06 +01:00
Bram Kragten
e0c4dc08a1 Tooltip tweak target picker (#7870) 2020-12-02 11:21:51 -06:00
Bram Kragten
8c655883fe Add device disabled reason (#7873) 2020-12-02 18:20:29 +01:00
Bram Kragten
ba90785115 Fix card picker cards (#7871) 2020-12-02 17:46:30 +01:00
Bram Kragten
7b392b626b Hardcode history card stub entity to sun.sun (#7760) 2020-12-02 10:16:46 -06:00
Zack Barett
8e4ceb7d48 Fix View Background Colors (#7823) 2020-12-02 17:08:18 +01:00
Philip Allgaier
2ab1c6e9a9 Make media player card work as square + add to gallery + icon pos tweaks (#7727) 2020-12-02 16:57:19 +01:00
Adam Ernst
dbdced0971 Improve descriptions for Configuration entries (#7606) 2020-12-02 16:36:21 +01:00
Philip Allgaier
5e481880bd Allow empty entities array with entity-filter (#7854) 2020-12-02 16:34:59 +01:00
Kendell R
faec063f34 Add --masonry-view-card-margin (#7395) 2020-12-02 16:22:20 +01:00
Zack Barett
bbea38d227 Lovelace Card Editor: Preview Background (#7678) 2020-12-02 16:19:01 +01:00
Kendell R
a0ef60de49 Make card picker border follow standards (#7162) 2020-12-02 16:14:46 +01:00
Philip Allgaier
3313572606 Improve password change card (#7756)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-12-02 16:00:49 +01:00
Philip Allgaier
c4f850cb14 Cleanup created user if person creation is cancelled (#7865) 2020-12-02 15:45:24 +01:00
Erik Montnemery
3bdab738c6 Support disabling devices (#7715)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-12-02 15:40:35 +01:00
Philip Allgaier
faaef31b9f Show proper error message if username already used (#7866) 2020-12-02 15:35:38 +01:00
Philip Allgaier
ca7b8b8b4c Add option to deactivate a user (#7757) 2020-12-02 15:33:11 +01:00
Philip Allgaier
9ca84e0694 Ensure "false" is set as default for "continue_on_timeout" (#7862) 2020-12-02 15:22:17 +01:00
Philip Allgaier
daaf2b1796 Ensure push notification description reacts to language change (#7856) 2020-12-02 12:11:47 +01:00
Bram Kragten
25f7cbea5a Add target selector (#7864)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-12-02 12:10:31 +01:00
Josh McCarty
c485ea9d7b Add number formatting to counter entity state display (#7868) 2020-12-02 11:51:56 +01:00
HomeAssistant Azure
295390c8e9 [ci skip] Translation update 2020-12-02 00:32:30 +00:00
Bram Kragten
3ebf816ce2 Fix height of ha-icon-input (#7767) 2020-12-01 22:51:15 +01:00
Bram Kragten
50e7410002 Merge pull request #7833 from home-assistant/dev 2020-11-27 00:16:56 +01:00
139 changed files with 7252 additions and 3239 deletions

View File

@@ -7,8 +7,8 @@ export const createMediaPlayerEntities = () => [
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
supported_features: 195135,
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
entity_picture: "/images/album_cover_2.jpg",
media_duration: 300,
media_position: 50,
@@ -24,8 +24,8 @@ export const createMediaPlayerEntities = () => [
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
supported_features: 195135,
entity_picture: "/images/album_cover.jpg",
media_duration: 300,
media_position: 0,

View File

@@ -146,6 +146,16 @@ const CONFIGS = [
entity: media_player.receiver_off
`,
},
{
heading: "Grid Full Size",
config: `
- type: grid
columns: 1
cards:
- type: media-control
entity: media_player.music_paused
`,
},
];
class DemoHuiMediControlCard extends PolymerElement {

View File

@@ -74,9 +74,7 @@ export class HassioUpdate extends LitElement {
"Supervisor",
this.supervisor.supervisor,
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${
this.supervisor.supervisor.version_latest
}`
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
)}
${this.supervisor.host.features.includes("hassos")
? this._renderUpdateCard(

View File

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

View File

@@ -0,0 +1,6 @@
export const ensureArray = (value?: any) => {
if (!value || Array.isArray(value)) {
return value;
}
return [value];
};

View File

@@ -67,6 +67,10 @@ export const computeStateDisplay = (
}
}
if (domain === "counter") {
return formatNumber(compareState, language);
}
return (
// Return device class translation
(stateObj.attributes.device_class &&

View File

@@ -1,8 +1,12 @@
export const copyToClipboard = (str) => {
const el = document.createElement("textarea");
el.value = str;
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);
if (navigator.clipboard) {
navigator.clipboard.writeText(str);
} else {
const el = document.createElement("textarea");
el.value = str;
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);
}
};

View File

@@ -139,7 +139,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
private _filteredDevices: DeviceRegistryEntry[] = [];
private _getDevices = memoizeOne(
private _getAreasWithDevices = memoizeOne(
(
devices: DeviceRegistryEntry[],
areas: AreaRegistryEntry[],
@@ -277,7 +277,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
if (!this._devices || !this._areas || !this._entities) {
return html``;
}
const areas = this._getDevices(
const areas = this._getAreasWithDevices(
this._devices,
this._areas,
this._entities,

View File

@@ -1,4 +1,5 @@
import "../ha-icon-button";
import "../ha-svg-icon";
import "@material/mwc-icon-button/mwc-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
@@ -12,6 +13,8 @@ import {
html,
LitElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
@@ -35,6 +38,7 @@ import {
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import { mdiClose, mdiMenuUp, mdiMenuDown } from "@mdi/js";
interface Device {
name: string;
@@ -111,6 +115,10 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean })
private _opened?: boolean;
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
private _init = false;
private _getDevices = memoizeOne(
(
devices: DeviceRegistryEntry[],
@@ -122,18 +130,27 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
deviceFilter: this["deviceFilter"]
): Device[] => {
if (!devices.length) {
return [];
return [
{
id: "",
area: "",
name: this.hass.localize("ui.components.device-picker.no_devices"),
},
];
}
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
continue;
if (includeDomains || excludeDomains || includeDeviceClasses) {
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
@@ -141,7 +158,9 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
areaLookup[area.area_id] = area;
}
let inputDevices = [...devices];
let inputDevices = devices.filter(
(device) => device.id === this.value || !device.disabled_by
);
if (includeDomains) {
inputDevices = inputDevices.filter((device) => {
@@ -208,6 +227,15 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
: this.hass.localize("ui.components.device-picker.no_area"),
};
});
if (!outputDevices.length) {
return [
{
id: "",
area: "",
name: this.hass.localize("ui.components.device-picker.no_match"),
},
];
}
if (outputDevices.length === 1) {
return outputDevices;
}
@@ -215,6 +243,18 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
}
);
public open() {
this.updateComplete.then(() => {
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
});
}
public focus() {
this.updateComplete.then(() => {
this.shadowRoot?.querySelector("paper-input")?.focus();
});
}
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
@@ -229,25 +269,33 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
];
}
protected updated(changedProps: PropertyValues) {
if (
(!this._init && this.devices && this.areas && this.entities) ||
(changedProps.has("_opened") && this._opened)
) {
this._init = true;
(this._comboBox as any).items = this._getDevices(
this.devices!,
this.areas!,
this.entities!,
this.includeDomains,
this.excludeDomains,
this.includeDeviceClasses,
this.deviceFilter
);
}
}
protected render(): TemplateResult {
if (!this.devices || !this.areas || !this.entities) {
return html``;
}
const devices = this._getDevices(
this.devices,
this.areas,
this.entities,
this.includeDomains,
this.excludeDomains,
this.includeDeviceClasses,
this.deviceFilter
);
return html`
<vaadin-combo-box-light
item-value-path="id"
item-id-path="id"
item-label-path="name"
.items=${devices}
.value=${this._value}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@@ -265,34 +313,30 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
>
${this.value
? html`
<ha-icon-button
aria-label=${this.hass.localize(
<mwc-icon-button
.label=${this.hass.localize(
"ui.components.device-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
Clear
</ha-icon-button>
`
: ""}
${devices.length > 0
? html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.show_devices"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</ha-icon-button>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
`
: ""}
<mwc-icon-button
.label=${this.hass.localize(
"ui.components.device-picker.show_devices"
)}
slot="suffix"
class="toggle-button"
>
<ha-svg-icon
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
></ha-svg-icon>
</mwc-icon-button>
</paper-input>
</vaadin-combo-box-light>
`;
@@ -329,7 +373,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResult {
return css`
paper-input > ha-icon-button {
paper-input > mwc-icon-button {
--mdc-icon-button-size: 24px;
padding: 2px;
color: var(--secondary-text-color);

View File

@@ -95,13 +95,24 @@ export class HaEntityPicker extends LitElement {
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
@property({ type: Boolean, attribute: "hide-clear-icon" })
public hideClearIcon = false;
@property({ type: Boolean }) public hideClearIcon = false;
@property({ type: Boolean }) private _opened = false;
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
public open() {
this.updateComplete.then(() => {
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
});
}
public focus() {
this.updateComplete.then(() => {
this.shadowRoot?.querySelector("paper-input")?.focus();
});
}
private _initedStates = false;
private _states: HassEntity[] = [];
@@ -154,6 +165,24 @@ export class HaEntityPicker extends LitElement {
);
}
if (!states.length) {
return [
{
entity_id: "",
state: "",
last_changed: "",
last_updated: "",
context: { id: "", user_id: null },
attributes: {
friendly_name: this.hass!.localize(
"ui.components.entity.entity-picker.no_match"
),
icon: "mdi:magnify",
},
},
];
}
return states;
}
);
@@ -204,7 +233,6 @@ export class HaEntityPicker extends LitElement {
.label=${this.label === undefined
? this.hass.localize("ui.components.entity.entity-picker.entity")
: this.label}
.value=${this._value}
.disabled=${this.disabled}
class="input"
autocapitalize="none"

View File

@@ -1,4 +1,5 @@
import "./ha-icon-button";
import "./ha-svg-icon";
import "@material/mwc-icon-button/mwc-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
@@ -14,6 +15,8 @@ import {
property,
internalProperty,
TemplateResult,
PropertyValues,
query,
} from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
import {
@@ -29,6 +32,18 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { PolymerChangedEvent } from "../polymer-types";
import { HomeAssistant } from "../types";
import memoizeOne from "memoize-one";
import {
DeviceEntityLookup,
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../data/entity_registry";
import { computeDomain } from "../common/entity/compute_domain";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
const rowRenderer = (
root: HTMLElement,
@@ -71,31 +86,250 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
@property() public placeholder?: string;
@property() public _areas?: AreaRegistryEntry[];
@property({ type: Boolean, attribute: "no-add" })
public noAdd?: boolean;
/**
* Show only areas with entities from specific domains.
* @type {Array}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show no areas with entities of these domains.
* @type {Array}
* @attr exclude-domains
*/
@property({ type: Array, attribute: "exclude-domains" })
public excludeDomains?: string[];
/**
* Show only areas with entities of these device classes.
* @type {Array}
* @attr include-device-classes
*/
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
@internalProperty() private _areas?: AreaRegistryEntry[];
@internalProperty() private _devices?: DeviceRegistryEntry[];
@internalProperty() private _entities?: EntityRegistryEntry[];
@internalProperty() private _opened?: boolean;
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
private _init = false;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeAreaRegistry(this.hass.connection!, (areas) => {
this._areas = this.noAdd
? areas
: [
...areas,
{
area_id: "add_new",
name: this.hass.localize("ui.components.area-picker.add_new"),
},
];
this._areas = areas;
}),
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
this._devices = devices;
}),
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
];
}
public open() {
this.updateComplete.then(() => {
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
});
}
public focus() {
this.updateComplete.then(() => {
this.shadowRoot?.querySelector("paper-input")?.focus();
});
}
private _getAreas = memoizeOne(
(
areas: AreaRegistryEntry[],
devices: DeviceRegistryEntry[],
entities: EntityRegistryEntry[],
includeDomains: this["includeDomains"],
excludeDomains: this["excludeDomains"],
includeDeviceClasses: this["includeDeviceClasses"],
deviceFilter: this["deviceFilter"],
entityFilter: this["entityFilter"],
noAdd: this["noAdd"]
): AreaRegistryEntry[] => {
if (!areas.length) {
return [
{
area_id: "",
name: this.hass.localize("ui.components.area-picker.no_areas"),
},
];
}
const deviceEntityLookup: DeviceEntityLookup = {};
let inputDevices: DeviceRegistryEntry[] | undefined;
let inputEntities: EntityRegistryEntry[] | undefined;
if (includeDomains || excludeDomains || includeDeviceClasses) {
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);
} else if (deviceFilter) {
inputDevices = devices;
} else if (entityFilter) {
inputEntities = entities.filter((entity) => entity.area_id);
}
if (includeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
}
if (excludeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return true;
}
return entities.every(
(entity) =>
!excludeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter(
(entity) => !excludeDomains.includes(computeDomain(entity.entity_id))
);
}
if (includeDeviceClasses) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
}
if (deviceFilter) {
inputDevices = inputDevices!.filter((device) => deviceFilter!(device));
}
if (entityFilter) {
inputEntities = inputEntities!.filter((entity) =>
entityFilter!(entity)
);
}
let outputAreas = areas;
let areaIds: string[] | undefined;
if (inputDevices) {
areaIds = inputDevices
.filter((device) => device.area_id)
.map((device) => device.area_id!);
}
if (inputEntities) {
areaIds = (areaIds ?? []).concat(
inputEntities
.filter((entity) => entity.area_id)
.map((entity) => entity.area_id!)
);
}
if (areaIds) {
outputAreas = areas.filter((area) => areaIds!.includes(area.area_id));
}
if (!outputAreas.length) {
outputAreas = [
{
area_id: "",
name: this.hass.localize("ui.components.area-picker.no_match"),
},
];
}
return noAdd
? outputAreas
: [
...outputAreas,
{
area_id: "add_new",
name: this.hass.localize("ui.components.area-picker.add_new"),
},
];
}
);
protected updated(changedProps: PropertyValues) {
if (
(!this._init && this._devices && this._areas && this._entities) ||
(changedProps.has("_opened") && this._opened)
) {
this._init = true;
(this._comboBox as any).items = this._getAreas(
this._areas!,
this._devices!,
this._entities!,
this.includeDomains,
this.excludeDomains,
this.includeDeviceClasses,
this.deviceFilter,
this.entityFilter,
this.noAdd
);
}
}
protected render(): TemplateResult {
if (!this._areas) {
if (!this._devices || !this._areas || !this._entities) {
return html``;
}
return html`
@@ -103,7 +337,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
item-value-path="area_id"
item-id-path="area_id"
item-label-path="name"
.items=${this._areas}
.value=${this._value}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@@ -124,34 +357,28 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
>
${this.value
? html`
<ha-icon-button
aria-label=${this.hass.localize(
<mwc-icon-button
.label=${this.hass.localize(
"ui.components.area-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
${this.hass.localize("ui.components.area-picker.clear")}
</ha-icon-button>
`
: ""}
${this._areas.length > 0
? html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.area-picker.show_areas"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
${this.hass.localize("ui.components.area-picker.toggle")}
</ha-icon-button>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
`
: ""}
<mwc-icon-button
.label=${this.hass.localize("ui.components.area-picker.toggle")}
slot="suffix"
class="toggle-button"
>
<ha-svg-icon
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
></ha-svg-icon>
</mwc-icon-button>
</paper-input>
</vaadin-combo-box-light>
`;
@@ -227,7 +454,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResult {
return css`
paper-input > ha-icon-button {
paper-input > mwc-icon-button {
--mdc-icon-button-size: 24px;
padding: 2px;
color: var(--secondary-text-color);

View File

@@ -52,6 +52,7 @@ class HaBluePrintPicker extends LitElement {
.label=${this.label ||
this.hass.localize("ui.components.blueprint-picker.label")}
.disabled=${this.disabled}
horizontal-align="left"
>
<paper-listbox
slot="dropdown-content"
@@ -110,6 +111,9 @@ class HaBluePrintPicker extends LitElement {
paper-listbox {
min-width: 200px;
}
paper-item {
cursor: pointer;
}
`;
}
}

View File

@@ -23,8 +23,6 @@ export class HaButtonMenu extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public fixed = false;
@query("mwc-menu", true) private _menu?: Menu;
public get items() {
@@ -41,7 +39,6 @@ export class HaButtonMenu extends LitElement {
<slot name="trigger"></slot>
</div>
<mwc-menu
.fixed=${this.fixed}
.corner=${this.corner}
.multi=${this.multi}
.activatable=${this.activatable}

View File

@@ -11,6 +11,7 @@ import {
import { fireEvent } from "../common/dom/fire_event";
import type { ToggleButton } from "../types";
import "./ha-svg-icon";
import "@material/mwc-button/mwc-button";
@customElement("ha-button-toggle-group")
export class HaButtonToggleGroup extends LitElement {
@@ -21,17 +22,22 @@ export class HaButtonToggleGroup extends LitElement {
protected render(): TemplateResult {
return html`
<div>
${this.buttons.map(
(button) => html`
<mwc-icon-button
.label=${button.label}
.value=${button.value}
?active=${this.active === button.value}
@click=${this._handleClick}
>
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
</mwc-icon-button>
`
${this.buttons.map((button) =>
button.iconPath
? html`<mwc-icon-button
.label=${button.label}
.value=${button.value}
?active=${this.active === button.value}
@click=${this._handleClick}
>
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
</mwc-icon-button>`
: html`<mwc-button
.value=${button.value}
?active=${this.active === button.value}
@click=${this._handleClick}
>${button.label}</mwc-button
>`
)}
</div>
`;
@@ -49,13 +55,15 @@ export class HaButtonToggleGroup extends LitElement {
--mdc-icon-button-size: var(--button-toggle-size, 36px);
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
}
mwc-icon-button {
mwc-icon-button,
mwc-button {
border: 1px solid var(--primary-color);
border-right-width: 0px;
position: relative;
cursor: pointer;
}
mwc-icon-button::before {
mwc-icon-button::before,
mwc-button::before {
top: 0;
left: 0;
width: 100%;
@@ -67,17 +75,21 @@ export class HaButtonToggleGroup extends LitElement {
content: "";
transition: opacity 15ms linear, background-color 15ms linear;
}
mwc-icon-button[active]::before {
mwc-icon-button[active]::before,
mwc-button[active]::before {
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
}
mwc-icon-button:first-child {
mwc-icon-button:first-child,
mwc-button:first-child {
border-radius: 4px 0 0 4px;
}
mwc-icon-button:last-child {
mwc-icon-button:last-child,
mwc-button:last-child {
border-radius: 0 4px 4px 0;
border-right-width: 1px;
}
mwc-icon-button:only-child {
mwc-icon-button:only-child,
mwc-button:only-child {
border-radius: 4px;
border-right-width: 1px;
}

View File

@@ -68,12 +68,6 @@ export class HaDialog extends MwcDialog {
top: var(--dialog-surface-top);
min-height: var(--mdc-dialog-min-height, auto);
}
:host([full]) .mdc-dialog .mdc-dialog__surface {
height: 100%;
width: 100%;
}
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
display: flex;
flex-direction: column;
@@ -96,23 +90,6 @@ export class HaDialog extends MwcDialog {
margin-left: 40px;
margin-right: 0px;
}
.mdc-dialog__content::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
.mdc-dialog__content::-webkit-scrollbar-thumb {
-webkit-border-radius: 4px;
border-radius: 4px;
background: var(--scrollbar-thumb-color);
}
.mdc-dialog__content {
overflow-y: auto;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;
}
`,
];
}

View File

@@ -1,4 +1,3 @@
import { mdiChevronDown } from "@mdi/js";
import {
css,
CSSResult,
@@ -9,9 +8,10 @@ import {
query,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-svg-icon";
import { mdiChevronDown } from "@mdi/js";
import { classMap } from "lit-html/directives/class-map";
@customElement("ha-expansion-panel")
class HaExpansionPanel extends LitElement {

View File

@@ -1,4 +1,4 @@
import { mdiHelpCircleOutline } from "@mdi/js";
import { mdiHelpCircle } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
@@ -18,7 +18,7 @@ export class HaHelpTooltip extends LitElement {
protected render(): TemplateResult {
return html`
<ha-svg-icon .path=${mdiHelpCircleOutline}></ha-svg-icon>
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
<paper-tooltip
offset="4"
.position=${this.position}

View File

@@ -127,7 +127,7 @@ class HaHLSPlayer extends LitElement {
// Parse playlist assuming it is a master playlist. Match group 1 is whether hevc, match group 2 is regular playlist url
// See https://tools.ietf.org/html/rfc8216 for HLS spec details
const playlistRegexp = /#EXT-X-STREAM-INF:.*?(?:CODECS=".*?(?<isHevc>hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(?<streamUrl>.+)/g;
const playlistRegexp = /#EXT-X-STREAM-INF:.*?(?:CODECS=".*?(hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(.+)/g;
const match = playlistRegexp.exec(masterPlaylist);
const matchTwice = playlistRegexp.exec(masterPlaylist);
@@ -136,17 +136,13 @@ class HaHLSPlayer extends LitElement {
let playlist_url: string;
if (match !== null && matchTwice === null) {
// Only send the regular playlist url if we match exactly once
playlist_url = new URL(match.groups!.streamUrl, this.url).href;
playlist_url = new URL(match[2], this.url).href;
} else {
playlist_url = this.url;
}
// If codec is HEVC and ExoPlayer is supported, use ExoPlayer.
if (
this._useExoPlayer &&
match !== null &&
match.groups!.isHevc !== undefined
) {
if (this._useExoPlayer && match !== null && match[1] !== undefined) {
this._renderHLSExoPlayer(playlist_url);
} else if (hls.isSupported()) {
this._renderHLSPolyfill(videoEl, hls, playlist_url);

View File

@@ -60,8 +60,9 @@ export class HaIconInput extends LitElement {
static get styles() {
return css`
ha-icon {
position: relative;
bottom: 4px;
position: absolute;
bottom: 2px;
right: 0;
}
`;
}

View File

@@ -0,0 +1,45 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { ActionSelector } from "../../data/selector";
import { Action } from "../../data/script";
import "../../panels/config/automation/action/ha-automation-action";
@customElement("ha-selector-action")
export class HaActionSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: ActionSelector;
@property() public value?: Action;
@property() public label?: string;
protected render() {
return html`<ha-automation-action
.actions=${this.value || []}
.hass=${this.hass}
></ha-automation-action>`;
}
static get styles(): CSSResult {
return css`
ha-automation-action {
display: block;
margin-bottom: 16px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-action": HaActionSelector;
}
}

View File

@@ -1,7 +1,16 @@
import { customElement, html, LitElement, property } from "lit-element";
import {
customElement,
html,
internalProperty,
LitElement,
property,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { AreaSelector } from "../../data/selector";
import "../ha-area-picker";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import { DeviceRegistryEntry } from "../../data/device_registry";
import { EntityRegistryEntry } from "../../data/entity_registry";
@customElement("ha-selector-area")
export class HaAreaSelector extends LitElement {
@@ -13,14 +22,77 @@ export class HaAreaSelector extends LitElement {
@property() public label?: string;
@internalProperty() public _configEntries?: ConfigEntry[];
protected updated(changedProperties) {
if (changedProperties.has("selector")) {
const oldSelector = changedProperties.get("selector");
if (
oldSelector !== this.selector &&
this.selector.area.device?.integration
) {
this._loadConfigEntries();
}
}
}
protected render() {
return html`<ha-area-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
no-add
.deviceFilter=${(device) => this._filterDevices(device)}
.entityFilter=${(entity) => this._filterEntities(entity)}
.includeDeviceClasses=${this.selector.area.entity?.device_class
? [this.selector.area.entity.device_class]
: undefined}
.includeDomains=${this.selector.area.entity?.domain
? [this.selector.area.entity.domain]
: undefined}
></ha-area-picker>`;
}
private _filterEntities(entity: EntityRegistryEntry): boolean {
if (this.selector.area.entity?.integration) {
if (entity.platform !== this.selector.area.entity.integration) {
return false;
}
}
return true;
}
private _filterDevices(device: DeviceRegistryEntry): boolean {
if (
this.selector.area.device?.manufacturer &&
device.manufacturer !== this.selector.area.device.manufacturer
) {
return false;
}
if (
this.selector.area.device?.model &&
device.model !== this.selector.area.device.model
) {
return false;
}
if (this.selector.area.device?.integration) {
if (
this._configEntries &&
!this._configEntries.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {
return false;
}
}
return true;
}
private async _loadConfigEntries() {
this._configEntries = (await getConfigEntries(this.hass)).filter(
(entry) => entry.domain === this.selector.area.device?.integration
);
}
}
declare global {

View File

@@ -63,7 +63,8 @@ export class HaDeviceSelector extends LitElement {
}
if (this.selector.device.integration) {
if (
!this._configEntries?.some((entry) =>
this._configEntries &&
!this._configEntries.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {

View File

@@ -19,7 +19,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
@property() public selector!: EntitySelector;
@internalProperty() private _entities?: Record<string, string>;
@internalProperty() private _entityPlaformLookup?: Record<string, string>;
@property() public value?: any;
@@ -45,7 +45,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
}
entityLookup[confEnt.entity_id] = confEnt.platform;
}
this._entities = entityLookup;
this._entityPlaformLookup = entityLookup;
}),
];
}
@@ -66,8 +66,9 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
}
if (this.selector.entity.integration) {
if (
!this._entities ||
this._entities[entity.entity_id] !== this.selector.entity.integration
!this._entityPlaformLookup ||
this._entityPlaformLookup[entity.entity_id] !==
this.selector.entity.integration
) {
return false;
}

View File

@@ -0,0 +1,153 @@
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { TargetSelector } from "../../data/selector";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import { DeviceRegistryEntry } from "../../data/device_registry";
import "../ha-target-picker";
import "@material/mwc-list/mwc-list-item";
import "@polymer/paper-input/paper-input";
import "@material/mwc-list/mwc-list";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { Target } from "../../data/target";
import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
@customElement("ha-selector-target")
export class HaTargetSelector extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public selector!: TargetSelector;
@property() public value?: Target;
@property() public label?: string;
@internalProperty() private _entityPlaformLookup?: Record<string, string>;
@internalProperty() private _configEntries?: ConfigEntry[];
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
const entityLookup = {};
for (const confEnt of entities) {
if (!confEnt.platform) {
continue;
}
entityLookup[confEnt.entity_id] = confEnt.platform;
}
this._entityPlaformLookup = entityLookup;
}),
];
}
protected updated(changedProperties) {
if (changedProperties.has("selector")) {
const oldSelector = changedProperties.get("selector");
if (
oldSelector !== this.selector &&
this.selector.target.device?.integration
) {
this._loadConfigEntries();
}
}
}
protected render() {
return html`<ha-target-picker
.hass=${this.hass}
.value=${this.value}
.deviceFilter=${(device) => this._filterDevices(device)}
.entityRegFilter=${(entity: EntityRegistryEntry) =>
this._filterRegEntities(entity)}
.entityFilter=${(entity: HassEntity) => this._filterEntities(entity)}
.includeDeviceClasses=${this.selector.target.entity?.device_class
? [this.selector.target.entity.device_class]
: undefined}
.includeDomains=${this.selector.target.entity?.domain
? [this.selector.target.entity.domain]
: undefined}
></ha-target-picker>`;
}
private _filterEntities(entity: HassEntity): boolean {
if (this.selector.target.entity?.integration) {
if (
!this._entityPlaformLookup ||
this._entityPlaformLookup[entity.entity_id] !==
this.selector.target.entity.integration
) {
return false;
}
}
return true;
}
private _filterRegEntities(entity: EntityRegistryEntry): boolean {
if (this.selector.target.entity?.integration) {
if (entity.platform !== this.selector.target.entity.integration) {
return false;
}
}
return true;
}
private _filterDevices(device: DeviceRegistryEntry): boolean {
if (
this.selector.target.device?.manufacturer &&
device.manufacturer !== this.selector.target.device.manufacturer
) {
return false;
}
if (
this.selector.target.device?.model &&
device.model !== this.selector.target.device.model
) {
return false;
}
if (this.selector.target.device?.integration) {
if (
!this._configEntries?.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {
return false;
}
}
return true;
}
private async _loadConfigEntries() {
this._configEntries = (await getConfigEntries(this.hass)).filter(
(entry) => entry.domain === this.selector.target.device?.integration
);
}
static get styles(): CSSResult {
return css`
ha-target-picker {
margin: 0 -8px;
display: block;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-target": HaTargetSelector;
}
}

View File

@@ -5,9 +5,11 @@ import { HomeAssistant } from "../../types";
import "./ha-selector-entity";
import "./ha-selector-device";
import "./ha-selector-area";
import "./ha-selector-target";
import "./ha-selector-number";
import "./ha-selector-boolean";
import "./ha-selector-time";
import "./ha-selector-action";
import { Selector } from "../../data/selector";
@customElement("ha-selector")

View File

@@ -1,4 +1,4 @@
import "@material/mwc-list/mwc-list-item";
import "@polymer/paper-item/paper-item-body";
import {
css,
CSSResult,
@@ -18,10 +18,13 @@ export class HaSettingsRow extends LitElement {
protected render(): SVGTemplateResult {
return html`
<mwc-list-item noninteractive ?twoline=${!this.threeLine}>
<paper-item-body
?two-line=${!this.threeLine}
?three-line=${this.threeLine}
>
<slot name="heading"></slot>
<span slot="secondary"><slot name="description"></slot></span>
</mwc-list-item>
<div secondary><slot name="description"></slot></div>
</paper-item-body>
<slot></slot>
`;
}
@@ -34,7 +37,14 @@ export class HaSettingsRow extends LitElement {
align-content: normal;
align-self: auto;
align-items: center;
justify-content: space-between;
}
paper-item-body {
padding: 8px 16px 8px 0;
}
paper-item-body[two-line] {
min-height: calc(
var(--paper-item-body-two-line-min-height, 72px) - 16px
);
}
:host([narrow]) {
align-items: normal;
@@ -42,12 +52,12 @@ export class HaSettingsRow extends LitElement {
border-top: 1px solid var(--divider-color);
padding-bottom: 8px;
}
mwc-list-item {
--mdc-list-side-padding: 0;
}
::slotted(ha-switch) {
padding: 16px 0;
}
div[secondary] {
white-space: normal;
}
`;
}
}

View File

@@ -0,0 +1,605 @@
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
query,
unsafeCSS,
} from "lit-element";
import { HomeAssistant } from "../types";
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import {
mdiSofa,
mdiDevices,
mdiClose,
mdiPlus,
mdiUnfoldMoreVertical,
} from "@mdi/js";
import "./ha-svg-icon";
import "./ha-icon";
import "@material/mwc-icon-button/mwc-icon-button";
import { classMap } from "lit-html/directives/class-map";
import "@material/mwc-button/mwc-button";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../data/area_registry";
import {
computeDeviceName,
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../data/entity_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { computeStateName } from "../common/entity/compute_state_name";
import { stateIcon } from "../common/entity/state_icon";
import { fireEvent } from "../common/dom/fire_event";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import { computeDomain } from "../common/entity/compute_domain";
import { Target } from "../data/target";
import { ensureArray } from "../common/ensure-array";
import "./entity/ha-entity-picker";
import "./device/ha-device-picker";
import "./ha-area-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "@polymer/paper-tooltip/paper-tooltip";
@customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public value?: Target;
@property() public label?: string;
/**
* Show only targets with entities from specific domains.
* @type {Array}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show only targets with entities of these device classes.
* @type {Array}
* @attr include-device-classes
*/
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
@property() public entityRegFilter?: (entity: EntityRegistryEntry) => boolean;
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
@internalProperty() private _areas?: { [areaId: string]: AreaRegistryEntry };
@internalProperty() private _devices?: {
[deviceId: string]: DeviceRegistryEntry;
};
@internalProperty() private _entities?: EntityRegistryEntry[];
@internalProperty() private _addMode?: "area_id" | "entity_id" | "device_id";
@query("#input") private _inputElement?;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeAreaRegistry(this.hass.connection!, (areas) => {
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
for (const area of areas) {
areaLookup[area.area_id] = area;
}
this._areas = areaLookup;
}),
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
for (const device of devices) {
deviceLookup[device.id] = device;
}
this._devices = deviceLookup;
}),
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
];
}
protected render() {
if (!this._areas || !this._devices || !this._entities) {
return html``;
}
return html`<div class="mdc-chip-set items">
${ensureArray(this.value?.area_id)?.map((area_id) => {
const area = this._areas![area_id];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
mdiSofa
);
})}
${ensureArray(this.value?.device_id)?.map((device_id) => {
const device = this._devices![device_id];
return this._renderChip(
"device_id",
device_id,
device ? computeDeviceName(device, this.hass) : device_id,
undefined,
mdiDevices
);
})}
${ensureArray(this.value?.entity_id)?.map((entity_id) => {
const entity = this.hass.states[entity_id];
return this._renderChip(
"entity_id",
entity_id,
entity ? computeStateName(entity) : entity_id,
entity ? stateIcon(entity) : undefined
);
})}
</div>
${this._renderPicker()}
<div class="mdc-chip-set">
<div
class="mdc-chip area_id add"
.type=${"area_id"}
@click=${this._showPicker}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}</span
>
</span>
</span>
</div>
<div
class="mdc-chip device_id add"
.type=${"device_id"}
@click=${this._showPicker}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}</span
>
</span>
</span>
</div>
<div
class="mdc-chip entity_id add"
.type=${"entity_id"}
@click=${this._showPicker}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}</span
>
</span>
</span>
</div>
</div>`;
}
private async _showPicker(ev) {
this._addMode = ev.currentTarget.type;
await this.updateComplete;
setTimeout(() => {
this._inputElement?.open();
this._inputElement?.focus();
}, 0);
}
private _renderChip(
type: string,
id: string,
name: string,
icon?: string,
iconPath?: string
) {
return html`
<div
class="mdc-chip ${classMap({
[type]: true,
})}"
>
${iconPath
? html`<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${iconPath}
></ha-svg-icon>`
: ""}
${icon
? html`<ha-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.icon=${icon}
></ha-icon>`
: ""}
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text">${name}</span>
</span>
</span>
${type === "entity_id"
? ""
: html` <span role="gridcell">
<mwc-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${"Expand"}
.id=${id}
.type=${type}
@click=${this._handleExpand}
>
<ha-svg-icon .path=${mdiUnfoldMoreVertical}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip class="expand" animation-delay="0"
>${this.hass.localize(
`ui.components.target-picker.expand_${type}`
)}</paper-tooltip
>
</span>`}
<span role="gridcell">
<mwc-icon-button
class="mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${"Remove"}
.id=${id}
.type=${type}
@click=${this._handleRemove}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip animation-delay="0"
>${this.hass.localize(
`ui.components.target-picker.remove_${type}`
)}</paper-tooltip
>
</span>
</div>
`;
}
private _renderPicker() {
switch (this._addMode) {
case "area_id":
return html`<ha-area-picker
.hass=${this.hass}
id="input"
.type=${"area_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
no-add
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-area-picker>`;
case "device_id":
return html`<ha-device-picker
.hass=${this.hass}
id="input"
.type=${"device_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-device-picker>`;
case "entity_id":
return html`<ha-entity-picker
.hass=${this.hass}
id="input"
.type=${"entity_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-entity-picker>`;
}
return html``;
}
private _targetPicked(ev) {
ev.stopPropagation();
if (!ev.detail.value) {
return;
}
const value = ev.detail.value;
const target = ev.currentTarget;
target.value = "";
this._addMode = undefined;
fireEvent(this, "value-changed", {
value: this.value
? {
...this.value,
[target.type]: this.value[target.type]
? [...ensureArray(this.value[target.type]), value]
: value,
}
: { [target.type]: value },
});
}
private _handleExpand(ev) {
const target = ev.currentTarget as any;
const newDevices: string[] = [];
const newEntities: string[] = [];
if (target.type === "area_id") {
Object.values(this._devices!).forEach((device) => {
if (
device.area_id === target.id &&
!this.value!.device_id?.includes(device.id) &&
this._deviceMeetsFilter(device)
) {
newDevices.push(device.id);
}
});
this._entities!.forEach((entity) => {
if (
entity.area_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else if (target.type === "device_id") {
this._entities!.forEach((entity) => {
if (
entity.device_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else {
return;
}
let value = this.value;
if (newEntities.length) {
value = this._addItems(value, "entity_id", newEntities);
}
if (newDevices.length) {
value = this._addItems(value, "device_id", newDevices);
}
value = this._removeItem(value, target.type, target.id);
fireEvent(this, "value-changed", { value });
}
private _handleRemove(ev) {
const target = ev.currentTarget as any;
fireEvent(this, "value-changed", {
value: this._removeItem(this.value, target.type, target.id),
});
}
private _addItems(
value: this["value"],
type: string,
ids: string[]
): this["value"] {
return {
...value,
[type]: value![type] ? ensureArray(value![type])!.concat(ids) : ids,
};
}
private _removeItem(
value: this["value"],
type: string,
id: string
): this["value"] {
const newVal = ensureArray(value![type])!.filter((val) => val !== id);
if (newVal.length) {
return {
...value,
[type]: newVal,
};
}
const val = { ...value }!;
delete val[type];
if (Object.keys(val).length) {
return val;
}
return undefined;
}
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
const devEntities = this._entities?.filter(
(entity) => entity.device_id === device.id
);
if (this.includeDomains) {
if (!devEntities || !devEntities.length) {
return false;
}
if (
!devEntities.some((entity) =>
this.includeDomains!.includes(computeDomain(entity.entity_id))
)
) {
return false;
}
}
if (this.includeDeviceClasses) {
if (!devEntities || !devEntities.length) {
return false;
}
if (
!devEntities.some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return (
stateObj.attributes.device_class &&
this.includeDeviceClasses!.includes(
stateObj.attributes.device_class
)
);
})
) {
return false;
}
}
if (this.deviceFilter) {
return this.deviceFilter(device);
}
return true;
}
private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean {
if (
this.includeDomains &&
!this.includeDomains.includes(computeDomain(entity.entity_id))
) {
return false;
}
if (this.includeDeviceClasses) {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
if (
!stateObj.attributes.device_class ||
!this.includeDeviceClasses!.includes(stateObj.attributes.device_class)
) {
return false;
}
}
if (this.entityRegFilter) {
return this.entityRegFilter(entity);
}
return true;
}
static get styles(): CSSResult {
return css`
${unsafeCSS(chipStyles)}
.mdc-chip {
color: var(--primary-text-color);
}
.items {
z-index: 2;
}
.mdc-chip.add {
color: rgba(0, 0, 0, 0.87);
}
.mdc-chip:not(.add) {
cursor: default;
}
.mdc-chip mwc-icon-button {
--mdc-icon-button-size: 24px;
display: flex;
align-items: center;
outline: none;
}
.mdc-chip mwc-icon-button ha-svg-icon {
border-radius: 50%;
background: var(--secondary-text-color);
}
.mdc-chip__icon.mdc-chip__icon--trailing {
width: 16px;
height: 16px;
--mdc-icon-size: 14px;
color: var(--card-background-color);
}
.mdc-chip__icon--leading {
display: flex;
align-items: center;
justify-content: center;
--mdc-icon-size: 20px;
border-radius: 50%;
padding: 6px;
margin-left: -14px !important;
}
.expand-btn {
margin-right: 0;
}
.mdc-chip.area_id:not(.add) {
border: 2px solid #fed6a4;
background: var(--card-background-color);
}
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.area_id.add {
background: #fed6a4;
}
.mdc-chip.device_id:not(.add) {
border: 2px solid #a8e1fb;
background: var(--card-background-color);
}
.mdc-chip.device_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.device_id.add {
background: #a8e1fb;
}
.mdc-chip.entity_id:not(.add) {
border: 2px solid #d2e7b9;
background: var(--card-background-color);
}
.mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.entity_id.add {
background: #d2e7b9;
}
.mdc-chip:hover {
z-index: 5;
}
paper-tooltip.expand {
min-width: 200px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-target-picker": HaTargetPicker;
}
}

View File

@@ -6,7 +6,7 @@ import { navigate } from "../common/navigate";
import { Context, HomeAssistant } from "../types";
import { BlueprintInput } from "./blueprint";
import { DeviceCondition, DeviceTrigger } from "./device_automation";
import { Action } from "./script";
import { Action, MODES } from "./script";
export interface AutomationEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
@@ -26,7 +26,7 @@ export interface ManualAutomationConfig {
trigger: Trigger[];
condition?: Condition[];
action: Action[];
mode?: "single" | "restart" | "queued" | "parallel";
mode?: typeof MODES[number];
max?: number;
}

View File

@@ -17,6 +17,7 @@ export interface DeviceRegistryEntry {
area_id?: string;
name_by_user?: string;
entry_type: "service" | null;
disabled_by: string | null;
}
export interface DeviceEntityLookup {
@@ -26,6 +27,7 @@ export interface DeviceEntityLookup {
export interface DeviceRegistryEntryMutableParams {
area_id?: string | null;
name_by_user?: string | null;
disabled_by?: string | null;
}
export const fallbackDeviceName = (

View File

@@ -7,13 +7,13 @@ import { navigate } from "../common/navigate";
import { HomeAssistant } from "../types";
import { Condition, Trigger } from "./automation";
export const MODES = ["single", "restart", "queued", "parallel"];
export const MODES = ["single", "restart", "queued", "parallel"] as const;
export const MODES_MAX = ["queued", "parallel"];
export interface ScriptEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
last_triggered: string;
mode: "single" | "restart" | "queued" | "parallel";
mode: typeof MODES[number];
current?: number;
max?: number;
};
@@ -23,7 +23,7 @@ export interface ScriptConfig {
alias: string;
sequence: Action[];
icon?: string;
mode?: "single" | "restart" | "queued" | "parallel";
mode?: typeof MODES[number];
max?: number;
}

View File

@@ -2,9 +2,11 @@ export type Selector =
| EntitySelector
| DeviceSelector
| AreaSelector
| TargetSelector
| NumberSelector
| BooleanSelector
| TimeSelector;
| TimeSelector
| ActionSelector;
export interface EntitySelector {
entity: {
@@ -19,13 +21,41 @@ export interface DeviceSelector {
integration?: string;
manufacturer?: string;
model?: string;
entity?: EntitySelector["entity"];
entity?: {
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
};
};
}
export interface AreaSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
area: {};
area: {
entity?: {
integration?: EntitySelector["entity"]["integration"];
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
};
device?: {
integration?: DeviceSelector["device"]["integration"];
manufacturer?: DeviceSelector["device"]["manufacturer"];
model?: DeviceSelector["device"]["model"];
};
};
}
export interface TargetSelector {
target: {
entity?: {
integration?: EntitySelector["entity"]["integration"];
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
};
device?: {
integration?: DeviceSelector["device"]["integration"];
manufacturer?: DeviceSelector["device"]["manufacturer"];
model?: DeviceSelector["device"]["model"];
};
};
}
export interface NumberSelector {
@@ -47,3 +77,8 @@ export interface TimeSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
time: {};
}
export interface ActionSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
action: {};
}

5
src/data/target.ts Normal file
View File

@@ -0,0 +1,5 @@
export interface Target {
entity_id?: string[];
device_id?: string[];
area_id?: string[];
}

View File

@@ -20,6 +20,7 @@ export interface User {
export interface UpdateUserParams {
name?: User["name"];
is_active?: User["is_active"];
group_ids?: User["group_ids"];
}

View File

@@ -17,6 +17,7 @@ import { PolymerChangedEvent } from "../../../polymer-types";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";
import { navigate } from "../../../common/navigate";
class DialogAreaDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -154,6 +155,8 @@ class DialogAreaDetail extends LitElement {
} finally {
this._submitting = false;
}
navigate(this, "/config/areas/dashboard");
}
static get styles(): CSSResult[] {

View File

@@ -39,7 +39,7 @@ export class HaWaitForTriggerAction extends LitElement
)}
>
<ha-switch
.checked=${continue_on_timeout}
.checked=${continue_on_timeout ?? true}
@change=${this._continueChanged}
></ha-switch>
</ha-formfield>

View File

@@ -18,13 +18,9 @@ import "@polymer/paper-input/paper-textarea";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "../../../components/entity/ha-entity-toggle";
import "@material/mwc-button/mwc-button";
import "./trigger/ha-automation-trigger";
import "./condition/ha-automation-condition";
import "./action/ha-automation-action";
import { fireEvent } from "../../../common/dom/fire_event";
import { haStyle } from "../../../resources/styles";
import { HassEntity } from "home-assistant-js-websocket";
import { navigate } from "../../../common/navigate";
import {
BlueprintOrError,
Blueprints,
@@ -63,7 +59,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
protected render() {
const blueprint = this._blueprint;
return html`<ha-config-section .isWide=${this.isWide}>
return html`<ha-config-section vertical .isWide=${this.isWide}>
${!this.narrow
? html` <span slot="header">${this.config.alias}</span> `
: ""}
@@ -119,7 +115,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
</ha-card>
</ha-config-section>
<ha-config-section .isWide=${this.isWide}>
<ha-config-section vertical .isWide=${this.isWide}>
<span slot="header"
>${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.header"
@@ -144,11 +140,6 @@ export class HaBlueprintAutomationEditor extends LitElement {
"ui.panel.config.automation.editor.blueprint.no_blueprints"
)
: html`<ha-circular-progress active></ha-circular-progress>`}
<mwc-button @click=${this._navigateBlueprints}>
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.manage_blueprints"
)}
</mwc-button>
</div>
${this.config.use_blueprint.path
@@ -157,41 +148,37 @@ export class HaBlueprintAutomationEditor extends LitElement {
There is an error in this Blueprint: ${blueprint.error}
</p>`
: html`${blueprint?.metadata.description
? html`<p>${blueprint.metadata.description}</p>`
? html`<p class="card-content pre-line">
${blueprint.metadata.description}
</p>`
: ""}
${blueprint?.metadata?.input &&
Object.keys(blueprint.metadata.input).length
? html`<h3>
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.inputs"
)}
</h3>
${Object.entries(blueprint.metadata.input).map(
([key, value]) =>
html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<span slot="description"
>${value?.description}</span
>
${value?.selector
? html`<ha-selector
.hass=${this.hass}
.selector=${value.selector}
.key=${key}
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ||
value?.default}
@value-changed=${this._inputChanged}
></ha-selector>`
: html`<paper-input
.key=${key}
.value=${this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]}
@value-changed=${this._inputChanged}
no-label-float
></paper-input>`}
</ha-settings-row>`
)}`
? Object.entries(blueprint.metadata.input).map(
([key, value]) =>
html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<span slot="description">${value?.description}</span>
${value?.selector
? html`<ha-selector
.hass=${this.hass}
.selector=${value.selector}
.key=${key}
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ||
value?.default}
@value-changed=${this._inputChanged}
></ha-selector>`
: html`<paper-input
.key=${key}
required
.value=${this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]}
@value-changed=${this._inputChanged}
no-label-float
></paper-input>`}
</ha-settings-row>`
)
: html`<p class="padding">
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_inputs"
@@ -237,12 +224,18 @@ export class HaBlueprintAutomationEditor extends LitElement {
) {
return;
}
const input = { ...this.config.use_blueprint.input, [key]: value };
if (value === "" || value === undefined) {
delete input[key];
}
fireEvent(this, "value-changed", {
value: {
...this.config!,
use_blueprint: {
...this.config.use_blueprint,
input: { ...this.config.use_blueprint.input, [key]: value },
input,
},
},
});
@@ -267,25 +260,18 @@ export class HaBlueprintAutomationEditor extends LitElement {
});
}
private _navigateBlueprints() {
navigate(this, "/config/blueprint");
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
ha-card {
overflow: hidden;
}
.padding {
padding: 16px;
}
.pre-line {
white-space: pre-line;
}
.blueprint-picker-container {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
h3 {
margin: 16px;
@@ -304,10 +290,10 @@ export class HaBlueprintAutomationEditor extends LitElement {
border-top: 1px solid var(--divider-color);
}
:host(:not([narrow])) ha-settings-row paper-input {
width: 50%;
width: 60%;
}
:host(:not([narrow])) ha-settings-row ha-selector {
width: 50%;
width: 60%;
}
`,
];

View File

@@ -32,6 +32,7 @@ import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import { showToast } from "../../../util/toast";
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
import { copyToClipboard } from "../../../common/util/copy-clipboard";
import {
AutomationConfig,
AutomationEntity,
@@ -206,6 +207,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
? html`<blueprint-automation-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.stateObj=${stateObj}
.config=${this._config}
@value-changed=${this._valueChanged}
@@ -213,6 +215,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
: html`<manual-automation-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.stateObj=${stateObj}
.config=${this._config}
@value-changed=${this._valueChanged}
@@ -394,7 +397,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
private async _copyYaml() {
if (this._editor?.yaml) {
navigator.clipboard.writeText(this._editor.yaml);
copyToClipboard(this._editor.yaml);
}
}

View File

@@ -12,6 +12,7 @@ import {
internalProperty,
query,
TemplateResult,
css,
} from "lit-element";
import "../../../components/ha-dialog";
import { haStyleDialog } from "../../../resources/styles";
@@ -73,7 +74,9 @@ class DialogImportBlueprint extends LitElement {
this._result.blueprint.metadata.domain
)}
<br />
${this._result.blueprint.metadata.description}
<p class="pre-line">
${this._result.blueprint.metadata.description}
</p>
${this._result.validation_errors
? html`
<p class="error">
@@ -199,8 +202,15 @@ class DialogImportBlueprint extends LitElement {
}
}
static get styles(): CSSResult {
return haStyleDialog;
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
.pre-line {
white-space: pre-line;
}
`,
];
}
}

View File

@@ -1,6 +1,6 @@
import "../../../components/ha-fab";
import "@material/mwc-icon-button";
import { mdiPlus, mdiHelpCircle, mdiDelete, mdiRobot } from "@mdi/js";
import { mdiHelpCircle, mdiDelete, mdiRobot, mdiDownload } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import {
CSSResult,
@@ -112,7 +112,6 @@ class HaBlueprintOverview extends LitElement {
create: {
title: "",
type: narrow ? "icon-button" : undefined,
width: narrow ? undefined : "180px",
template: (_, blueprint: any) =>
blueprint.error
? ""
@@ -126,8 +125,9 @@ class HaBlueprintOverview extends LitElement {
"ui.panel.config.blueprint.overview.use_blueprint"
)}
@click=${(ev) => this._createNew(ev)}
><ha-svg-icon .path=${mdiRobot}></ha-svg-icon
></mwc-icon-button>`
>
<ha-svg-icon .path=${mdiRobot}></ha-svg-icon>
</mwc-icon-button>`
: html`<mwc-button
.blueprint=${blueprint}
@click=${(ev) => this._createNew(ev)}
@@ -182,7 +182,7 @@ class HaBlueprintOverview extends LitElement {
extended
@click=${this._addBlueprint}
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
</ha-fab>
</hass-tabs-subpage-data-table>
`;
@@ -195,7 +195,10 @@ class HaBlueprintOverview extends LitElement {
${this.hass.localize("ui.panel.config.blueprint.overview.introduction")}
<p>
<a
href="${documentationUrl(this.hass, "/docs/blueprint/editor/")}"
href="${documentationUrl(
this.hass,
"/docs/automation/using_blueprints/"
)}"
target="_blank"
rel="noreferrer"
>

View File

@@ -8,7 +8,6 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -31,7 +30,7 @@ export class HaDeviceEntitiesCard extends LitElement {
@property() public entities!: EntityRegistryStateEntry[];
@internalProperty() private _showDisabled = false;
@property() public showDisabled = false;
private _entityRows: Array<LovelaceRow | HuiErrorCard> = [];
@@ -68,7 +67,7 @@ export class HaDeviceEntitiesCard extends LitElement {
})}
</div>
${disabledEntities.length
? !this._showDisabled
? !this.showDisabled
? html`
<button
class="show-more"
@@ -119,7 +118,7 @@ export class HaDeviceEntitiesCard extends LitElement {
}
private _toggleShowDisabled() {
this._showDisabled = !this._showDisabled;
this.showDisabled = !this.showDisabled;
}
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
@@ -227,3 +226,9 @@ export class HaDeviceEntitiesCard extends LitElement {
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-entities-card": HaDeviceEntitiesCard;
}
}

View File

@@ -19,10 +19,11 @@ import {
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
import { HomeAssistant } from "../../../../types";
import type { HaSwitch } from "../../../../components/ha-switch";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { computeDeviceName } from "../../../../data/device_registry";
import { fireEvent } from "../../../../common/dom/fire_event";
import { haStyleDialog } from "../../../../resources/styles";
import { haStyle, haStyleDialog } from "../../../../resources/styles";
@customElement("dialog-device-registry-detail")
class DialogDeviceRegistryDetail extends LitElement {
@@ -36,6 +37,8 @@ class DialogDeviceRegistryDetail extends LitElement {
@internalProperty() private _areaId?: string;
@internalProperty() private _disabledBy!: string | null;
@internalProperty() private _submitting?: boolean;
public async showDialog(
@@ -45,6 +48,7 @@ class DialogDeviceRegistryDetail extends LitElement {
this._error = undefined;
this._nameByUser = this._params.device.name_by_user || "";
this._areaId = this._params.device.area_id;
this._disabledBy = this._params.device.disabled_by;
await this.updateComplete;
}
@@ -80,6 +84,32 @@ class DialogDeviceRegistryDetail extends LitElement {
.value=${this._areaId}
@value-changed=${this._areaPicked}
></ha-area-picker>
<div class="row">
<ha-switch
.checked=${!this._disabledBy}
@change=${this._disabledByChanged}
>
</ha-switch>
<div>
<div>
${this.hass.localize("ui.panel.config.devices.enabled_label")}
</div>
<div class="secondary">
${this._disabledBy && this._disabledBy !== "user"
? this.hass.localize(
"ui.panel.config.devices.enabled_cause",
"cause",
this.hass.localize(
`config_entry.disabled_by.${this._disabledBy}`
)
)
: ""}
${this.hass.localize(
"ui.panel.config.devices.enabled_description"
)}
</div>
</div>
</div>
</div>
</div>
<mwc-button
@@ -109,12 +139,17 @@ class DialogDeviceRegistryDetail extends LitElement {
this._areaId = event.detail.value;
}
private _disabledByChanged(ev: Event): void {
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user";
}
private async _updateEntry(): Promise<void> {
this._submitting = true;
try {
await this._params!.updateEntry({
name_by_user: this._nameByUser.trim() || null,
area_id: this._areaId || null,
disabled_by: this._disabledBy || null,
});
this._params = undefined;
} catch (err) {
@@ -128,6 +163,7 @@ class DialogDeviceRegistryDetail extends LitElement {
static get styles(): CSSResult[] {
return [
haStyle,
haStyleDialog,
css`
.form {
@@ -139,6 +175,15 @@ class DialogDeviceRegistryDetail extends LitElement {
.error {
color: var(--error-color);
}
ha-switch {
margin-right: 16px;
}
.row {
margin-top: 8px;
color: var(--primary-text-color);
display: flex;
align-items: center;
}
`,
];
}

View File

@@ -46,6 +46,7 @@ import "./device-detail/ha-device-entities-card";
import "./device-detail/ha-device-info-card";
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
import { brandsUrl } from "../../../util/brands-url";
import { haStyle } from "../../../resources/styles";
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string | null;
@@ -246,6 +247,28 @@ export class HaConfigDevicePage extends LitElement {
.devices=${this.devices}
.device=${device}
>
${
device.disabled_by
? html`
<div>
<p class="warning">
${this.hass.localize(
"ui.panel.config.devices.enabled_cause",
"cause",
this.hass.localize(
`ui.panel.config.devices.disabled_by.${device.disabled_by}`
)
)}
</p>
</div>
<div class="card-actions" slot="actions">
<mwc-button unelevated @click=${this._enableDevice}>
${this.hass.localize("ui.common.enable")}
</mwc-button>
</div>
`
: html``
}
${this._renderIntegrationInfo(device, integrations)}
</ha-device-info-card>
@@ -255,6 +278,7 @@ export class HaConfigDevicePage extends LitElement {
<ha-device-entities-card
.hass=${this.hass}
.entities=${entities}
.showDisabled=${device.disabled_by !== null}
>
</ha-device-entities-card>
`
@@ -272,9 +296,14 @@ export class HaConfigDevicePage extends LitElement {
)}
<ha-icon-button
@click=${this._showAutomationDialog}
title=${this.hass.localize(
"ui.panel.config.devices.automation.create"
)}
.disabled=${device.disabled_by}
title=${device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.automation.create_disabled"
)
: this.hass.localize(
"ui.panel.config.devices.automation.create"
)}
icon="hass:plus-circle"
></ha-icon-button>
</h1>
@@ -342,9 +371,16 @@ export class HaConfigDevicePage extends LitElement {
<ha-icon-button
@click=${this._createScene}
title=${this.hass.localize(
"ui.panel.config.devices.scene.create"
)}
.disabled=${device.disabled_by}
title=${
device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.scene.create_disabled"
)
: this.hass.localize(
"ui.panel.config.devices.scene.create"
)
}
icon="hass:plus-circle"
></ha-icon-button>
</h1>
@@ -415,9 +451,14 @@ export class HaConfigDevicePage extends LitElement {
)}
<ha-icon-button
@click=${this._showScriptDialog}
title=${this.hass.localize(
"ui.panel.config.devices.script.create"
)}
.disabled=${device.disabled_by}
title=${device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.script.create_disabled"
)
: this.hass.localize(
"ui.panel.config.devices.script.create"
)}
icon="hass:plus-circle"
></ha-icon-button>
</h1>
@@ -632,128 +673,137 @@ export class HaConfigDevicePage extends LitElement {
});
}
static get styles(): CSSResult {
return css`
.container {
display: flex;
flex-wrap: wrap;
margin: auto;
max-width: 1000px;
margin-top: 32px;
margin-bottom: 32px;
}
private async _enableDevice(): Promise<void> {
await updateDeviceRegistryEntry(this.hass, this.deviceId, {
disabled_by: null,
});
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.container {
display: flex;
flex-wrap: wrap;
margin: auto;
max-width: 1000px;
margin-top: 32px;
margin-bottom: 32px;
}
.card-header ha-icon-button {
margin-right: -8px;
color: var(--primary-color);
height: auto;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.device-info {
padding: 16px;
}
.card-header ha-icon-button {
margin-right: -8px;
color: var(--primary-color);
height: auto;
}
.show-more {
}
.device-info {
padding: 16px;
}
h1 {
margin: 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);
}
.show-more {
}
.header {
display: flex;
justify-content: space-between;
}
h1 {
margin: 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);
}
.column,
.fullwidth {
padding: 8px;
box-sizing: border-box;
}
.column {
width: 33%;
flex-grow: 1;
}
.fullwidth {
width: 100%;
flex-grow: 1;
}
.header {
display: flex;
justify-content: space-between;
}
.header-right {
align-self: center;
}
.column,
.fullwidth {
padding: 8px;
box-sizing: border-box;
}
.column {
width: 33%;
flex-grow: 1;
}
.fullwidth {
width: 100%;
flex-grow: 1;
}
.header-right img {
height: 30px;
}
.header-right {
align-self: center;
}
.header-right {
display: flex;
}
.header-right img {
height: 30px;
}
.header-right:first-child {
width: 100%;
justify-content: flex-end;
}
.header-right {
display: flex;
}
.header-right > *:not(:first-child) {
margin-left: 16px;
}
.header-right:first-child {
width: 100%;
justify-content: flex-end;
}
.battery {
align-self: center;
align-items: center;
display: flex;
}
.header-right > *:not(:first-child) {
margin-left: 16px;
}
.column > *:not(:first-child) {
margin-top: 16px;
}
.battery {
align-self: center;
align-items: center;
display: flex;
}
:host([narrow]) .column {
width: 100%;
}
.column > *:not(:first-child) {
margin-top: 16px;
}
:host([narrow]) .container {
margin-top: 0;
}
:host([narrow]) .column {
width: 100%;
}
paper-item {
cursor: pointer;
font-size: var(--paper-font-body1_-_font-size);
}
:host([narrow]) .container {
margin-top: 0;
}
paper-item.no-link {
cursor: default;
}
paper-item {
cursor: pointer;
font-size: var(--paper-font-body1_-_font-size);
}
a {
text-decoration: none;
color: var(--primary-color);
}
paper-item.no-link {
cursor: default;
}
ha-card {
padding-bottom: 8px;
}
a {
text-decoration: none;
color: var(--primary-color);
}
ha-card a {
color: var(--primary-text-color);
}
`;
ha-card {
padding-bottom: 8px;
}
ha-card a {
color: var(--primary-text-color);
}
`,
];
}
}

View File

@@ -1,5 +1,9 @@
import { mdiPlus } from "@mdi/js";
import { mdiPlus, mdiFilterVariant, mdiCancel } from "@mdi/js";
import "@material/mwc-list/mwc-list-item";
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
@@ -7,7 +11,9 @@ import {
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize";
@@ -18,6 +24,7 @@ import {
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-button-menu";
import { AreaRegistryEntry } from "../../../data/area_registry";
import { ConfigEntry } from "../../../data/config_entries";
import {
@@ -34,6 +41,7 @@ import { domainToName } from "../../../data/integration";
import "../../../layouts/hass-tabs-subpage-data-table";
import { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
import { haStyle } from "../../../resources/styles";
interface DeviceRowData extends DeviceRegistryEntry {
device?: DeviceRowData;
@@ -64,6 +72,12 @@ export class HaConfigDeviceDashboard extends LitElement {
window.location.search
);
@internalProperty() private _showDisabled = false;
@internalProperty() private _filter = "";
@internalProperty() private _numHiddenDevices = 0;
private _activeFilters = memoizeOne(
(
entries: ConfigEntry[],
@@ -74,6 +88,10 @@ export class HaConfigDeviceDashboard extends LitElement {
filters.forEach((value, key) => {
switch (key) {
case "config_entry": {
// If we are requested to show the devices for a given config entry,
// also show the disabled ones by default.
this._showDisabled = true;
const configEntry = entries.find(
(entry) => entry.entry_id === value
);
@@ -105,6 +123,7 @@ export class HaConfigDeviceDashboard extends LitElement {
entities: EntityRegistryEntry[],
areas: AreaRegistryEntry[],
filters: URLSearchParams,
showDisabled: boolean,
localize: LocalizeFunc
) => {
// Some older installations might have devices pointing at invalid entryIDs
@@ -117,6 +136,9 @@ export class HaConfigDeviceDashboard extends LitElement {
deviceLookup[device.id] = device;
}
// If nothing gets filtered, this is our correct count of devices
let startLength = outputDevices.length;
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
@@ -145,6 +167,7 @@ export class HaConfigDeviceDashboard extends LitElement {
outputDevices = outputDevices.filter((device) =>
device.config_entries.includes(value)
);
startLength = outputDevices.length;
const configEntry = entries.find((entry) => entry.entry_id === value);
if (configEntry) {
filterDomains.push(configEntry.domain);
@@ -152,6 +175,10 @@ export class HaConfigDeviceDashboard extends LitElement {
}
});
if (!showDisabled) {
outputDevices = outputDevices.filter((device) => !device.disabled_by);
}
outputDevices = outputDevices.map((device) => {
return {
...device,
@@ -182,16 +209,19 @@ export class HaConfigDeviceDashboard extends LitElement {
};
});
this._numHiddenDevices = startLength - outputDevices.length;
return { devicesOutput: outputDevices, filteredDomains: filterDomains };
}
);
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => {
(narrow: boolean, showDisabled: boolean): DataTableColumnContainer => {
const columns: DataTableColumnContainer = narrow
? {
name: {
title: "Device",
title: this.hass.localize(
"ui.panel.config.devices.data_table.device"
),
sortable: true,
filterable: true,
direction: "asc",
@@ -277,6 +307,24 @@ export class HaConfigDeviceDashboard extends LitElement {
: html` - `;
},
};
if (showDisabled) {
columns.disabled_by = {
title: "",
type: "icon",
template: (disabled_by) =>
disabled_by
? html`<div
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon .path=${mdiCancel}></ha-svg-icon>
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize("ui.panel.config.devices.disabled")}
</paper-tooltip>
</div>`
: "",
};
}
return columns;
}
);
@@ -298,9 +346,119 @@ export class HaConfigDeviceDashboard extends LitElement {
this.entities,
this.areas,
this._searchParms,
this._showDisabled,
this.hass.localize
);
const includeZHAFab = filteredDomains.includes("zha");
const activeFilters = this._activeFilters(
this.entries,
this._searchParms,
this.hass.localize
);
const headerToolbar = html`
<search-input
no-label-float
no-underline
@value-changed=${this._handleSearchChange}
.filter=${this._filter}
.label=${this.hass.localize("ui.panel.config.devices.picker.search")}
></search-input
>${activeFilters
? html`<div class="active-filters">
${this.narrow
? html` <div>
<ha-icon icon="hass:filter-variant"></ha-icon>
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.filtering.filtering_by"
)}
${activeFilters.join(", ")}
${this._numHiddenDevices
? "(" +
this.hass.localize(
"ui.panel.config.devices.picker.filter.hidden_devices",
"number",
this._numHiddenDevices
) +
")"
: ""}
</paper-tooltip>
</div>`
: `${this.hass.localize(
"ui.panel.config.filtering.filtering_by"
)} ${activeFilters.join(", ")}
${
this._numHiddenDevices
? "(" +
this.hass.localize(
"ui.panel.config.devices.picker.filter.hidden_devices",
"number",
this._numHiddenDevices
) +
")"
: ""
}
`}
<mwc-button @click=${this._clearFilter}
>${this.hass.localize(
"ui.panel.config.filtering.clear"
)}</mwc-button
>
</div>`
: ""}
${this._numHiddenDevices && !activeFilters
? html`<div class="active-filters">
${this.narrow
? html` <div>
<ha-icon icon="hass:filter-variant"></ha-icon>
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.devices.picker.filter.hidden_devices",
"number",
this._numHiddenDevices
)}
</paper-tooltip>
</div>`
: `${this.hass.localize(
"ui.panel.config.devices.picker.filter.hidden_devices",
"number",
this._numHiddenDevices
)}`}
<mwc-button @click=${this._showAll}
>${this.hass.localize(
"ui.panel.config.devices.picker.filter.show_all"
)}</mwc-button
>
</div>`
: ""}
<ha-button-menu corner="BOTTOM_START" multi>
<mwc-icon-button
slot="trigger"
.label=${this.hass!.localize(
"ui.panel.config.devices.picker.filter.filter"
)}
.title=${this.hass!.localize(
"ui.panel.config.devices.picker.filter.filter"
)}
>
<ha-svg-icon .path=${mdiFilterVariant}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item
@request-selected="${this._showDisabledChanged}"
graphic="control"
.selected=${this._showDisabled}
>
<ha-checkbox
slot="graphic"
.checked=${this._showDisabled}
></ha-checkbox>
${this.hass!.localize(
"ui.panel.config.devices.picker.filter.show_disabled"
)}
</mwc-list-item>
</ha-button-menu>
`;
return html`
<hass-tabs-subpage-data-table
@@ -311,13 +469,9 @@ export class HaConfigDeviceDashboard extends LitElement {
: "/config"}
.tabs=${configSections.integrations}
.route=${this.route}
.columns=${this._columns(this.narrow)}
.columns=${this._columns(this.narrow, this._showDisabled)}
.data=${devicesOutput}
.activeFilters=${this._activeFilters(
this.entries,
this._searchParms,
this.hass.localize
)}
.filter=${this._filter}
@row-click=${this._handleRowClicked}
clickable
.hasFab=${includeZHAFab}
@@ -333,6 +487,15 @@ export class HaConfigDeviceDashboard extends LitElement {
</ha-fab>
</a>`
: html``}
<div
class=${classMap({
"search-toolbar": this.narrow,
"table-header": !this.narrow,
})}
slot="header"
>
${headerToolbar}
</div>
</hass-tabs-subpage-data-table>
`;
}
@@ -363,6 +526,136 @@ export class HaConfigDeviceDashboard extends LitElement {
const deviceId = ev.detail.id;
navigate(this, `/config/devices/device/${deviceId}`);
}
private _showDisabledChanged(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "property") {
return;
}
this._showDisabled = ev.detail.selected;
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
}
private _clearFilter() {
navigate(this, window.location.pathname, true);
}
private _showAll() {
this._showDisabled = true;
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
a {
color: var(--primary-color);
}
h2 {
margin-top: 0;
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
p {
font-family: var(--paper-font-subhead_-_font-family);
-webkit-font-smoothing: var(
--paper-font-subhead_-_-webkit-font-smoothing
);
font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height);
}
ha-data-table {
width: 100%;
--data-table-border-width: 0;
}
:host(:not([narrow])) ha-data-table {
height: calc(100vh - 1px - var(--header-height));
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: "";
}
`,
];
}
}
declare global {

View File

@@ -111,10 +111,19 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
return html`
${!stateObj
? html`
<div class="container">
<div class="container warning">
${this.hass!.localize(
"ui.dialogs.entity_registry.editor.unavailable"
)}
${this._device?.disabled_by
? html`<br />${this.hass!.localize(
"ui.dialogs.entity_registry.editor.device_disabled"
)}<br /><mwc-button @click=${this._openDeviceSettings}>
${this.hass!.localize(
"ui.dialogs.entity_registry.editor.open_device_settings"
)}
</mwc-button>`
: ""}
</div>
`
: ""}
@@ -161,6 +170,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
<div class="row">
<ha-switch
.checked=${!this._disabledBy}
.disabled=${this._device?.disabled_by}
@change=${this._disabledByChanged}
>
</ha-switch>

View File

@@ -189,9 +189,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
? (name, entity: any) =>
html`
${name}<br />
${entity.entity_id} |
${this.hass.localize(`component.${entity.platform}.title`) ||
entity.platform}
<div class="secondary">
${entity.entity_id} |
${this.hass.localize(`component.${entity.platform}.title`) ||
entity.platform}
</div>
`
: undefined,
},

View File

@@ -5,6 +5,8 @@ import { classMap } from "lit-html/directives/class-map";
export class HaConfigSection extends LitElement {
@property() public isWide = false;
@property({ type: Boolean }) public vertical = false;
protected render() {
return html`
<div
@@ -16,8 +18,8 @@ export class HaConfigSection extends LitElement {
<div
class="together layout ${classMap({
narrow: !this.isWide,
vertical: !this.isWide,
horizontal: this.isWide,
vertical: this.vertical || !this.isWide,
horizontal: !this.vertical && this.isWide,
})}"
>
<div class="intro"><slot name="introduction"></slot></div>

View File

@@ -148,7 +148,7 @@ export class HaConfigHelpers extends LitElement {
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.automation}
.tabs=${configSections.helpers}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._getItems(this._stateItems)}
@row-click=${this._openEditDialog}

View File

@@ -68,6 +68,8 @@ class DialogPersonDetail extends LitElement {
@internalProperty() private _submitting = false;
@internalProperty() private _personExists = false;
private _deviceTrackersAvailable = memoizeOne((hass) => {
return Object.keys(hass.states).some(
(entityId) =>
@@ -79,6 +81,7 @@ class DialogPersonDetail extends LitElement {
this._params = params;
this._error = undefined;
if (this._params.entry) {
this._personExists = true;
this._name = this._params.entry.name || "";
this._userId = this._params.entry.user_id || undefined;
this._deviceTrackers = this._params.entry.device_trackers || [];
@@ -88,6 +91,7 @@ class DialogPersonDetail extends LitElement {
: undefined;
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
} else {
this._personExists = false;
this._name = "";
this._userId = undefined;
this._user = undefined;
@@ -398,6 +402,7 @@ class DialogPersonDetail extends LitElement {
await this._params!.updateEntry(values);
} else {
await this._params!.createEntry(values);
this._personExists = true;
}
this._params = undefined;
} catch (err) {
@@ -422,6 +427,14 @@ class DialogPersonDetail extends LitElement {
}
private _close(): void {
// If we do not have a person ID yet (= person creation dialog was just cancelled), but
// we already created a user ID for it, delete it now to not have it "free floating".
if (!this._personExists && this._userId) {
deleteUser(this.hass, this._userId);
this._params?.refreshUsers();
this._userId = undefined;
}
this._params = undefined;
}

View File

@@ -35,6 +35,7 @@ import "../../../components/ha-icon-input";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
import { copyToClipboard } from "../../../common/util/copy-clipboard";
import {
Action,
deleteScript,
@@ -545,7 +546,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
private async _copyYaml() {
if (this._editor?.yaml) {
navigator.clipboard.writeText(this._editor.yaml);
copyToClipboard(this._editor.yaml);
}
}

View File

@@ -241,7 +241,7 @@ export class DialogAddUser extends LitElement {
user = userResponse.user;
} catch (err) {
this._loading = false;
this._error = err.code;
this._error = err.message;
return;
}
@@ -255,7 +255,7 @@ export class DialogAddUser extends LitElement {
} catch (err) {
await deleteUser(this.hass, user.id);
this._loading = false;
this._error = err.code;
this._error = err.message;
return;
}

View File

@@ -13,6 +13,7 @@ import {
} from "lit-element";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-help-tooltip";
import "../../../components/ha-formfield";
import "../../../components/ha-switch";
import { adminChangePassword } from "../../../data/auth";
@@ -37,6 +38,8 @@ class DialogUserDetail extends LitElement {
@internalProperty() private _isAdmin?: boolean;
@internalProperty() private _isActive?: boolean;
@internalProperty() private _error?: string;
@internalProperty() private _params?: UserDetailDialogParams;
@@ -48,6 +51,7 @@ class DialogUserDetail extends LitElement {
this._error = undefined;
this._name = params.entry.name || "";
this._isAdmin = params.entry.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
this._isActive = params.entry.is_active;
await this.updateComplete;
}
@@ -91,15 +95,6 @@ class DialogUserDetail extends LitElement {
</span>
`
: ""}
${user.is_active
? html`
<span class="state"
>${this.hass.localize(
"ui.panel.config.users.editor.active"
)}</span
>
`
: ""}
</div>
<div class="form">
<paper-input
@@ -110,17 +105,21 @@ class DialogUserDetail extends LitElement {
"ui.panel.config.users.editor.name"
)}"
></paper-input>
<ha-formfield
.label=${this.hass.localize("ui.panel.config.users.editor.admin")}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
<div class="row">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.users.editor.admin"
)}
.dir=${computeRTLDirection(this.hass)}
>
</ha-switch>
</ha-formfield>
<ha-switch
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>
</ha-switch>
</ha-formfield>
</div>
${!this._isAdmin
? html`
<br />
@@ -129,6 +128,27 @@ class DialogUserDetail extends LitElement {
)}
`
: ""}
<div class="row">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.users.editor.active"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isActive}
@change=${this._activeChanged}
>
</ha-switch>
</ha-formfield>
<ha-help-tooltip
.label=${this.hass.localize(
"ui.panel.config.users.editor.active_tooltip"
)}
>
</ha-help-tooltip>
</div>
</div>
</div>
@@ -192,11 +212,16 @@ class DialogUserDetail extends LitElement {
this._isAdmin = ev.target.checked;
}
private async _activeChanged(ev): Promise<void> {
this._isActive = ev.target.checked;
}
private async _updateEntry() {
this._submitting = true;
try {
await this._params!.updateEntry({
name: this._name.trim(),
is_active: this._isActive,
group_ids: [
this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER,
],
@@ -293,8 +318,13 @@ class DialogUserDetail extends LitElement {
.state:not(:first-child) {
margin-left: 8px;
}
ha-switch {
margin-top: 8px;
.row {
display: flex;
padding: 8px 0;
}
ha-help-tooltip {
margin-left: 4px;
position: relative;
}
`,
];

View File

@@ -46,10 +46,17 @@ export class HaConfigUsers extends LitElement {
width: "25%",
direction: "asc",
grows: true,
template: (name) => html`
${name ||
this.hass!.localize("ui.panel.config.users.editor.unnamed_user")}
`,
template: (name, user: any) =>
narrow
? html` ${name}<br />
<div class="secondary">
${user.username} |
${this.hass.localize(`groups.${user.group_ids[0]}`)}
</div>`
: html` ${name ||
this.hass!.localize(
"ui.panel.config.users.editor.unnamed_user"
)}`,
},
username: {
title: this.hass.localize(
@@ -59,6 +66,7 @@ export class HaConfigUsers extends LitElement {
filterable: true,
width: "20%",
direction: "asc",
hidden: narrow,
template: (username) => html`
${username ||
this.hass!.localize("ui.panel.config.users.editor.unnamed_user")}
@@ -71,13 +79,24 @@ export class HaConfigUsers extends LitElement {
sortable: true,
filterable: true,
width: "20%",
direction: "asc",
hidden: narrow,
template: (groupIds) => html`
${this.hass.localize(`groups.${groupIds[0]}`)}
`,
},
};
if (!narrow) {
columns.system_generated = {
is_active: {
title: this.hass.localize(
"ui.panel.config.users.picker.headers.is_active"
),
type: "icon",
sortable: true,
filterable: true,
width: "80px",
template: (is_active) =>
is_active ? html`<ha-icon icon="hass:check"> </ha-icon>` : "",
},
system_generated: {
title: this.hass.localize(
"ui.panel.config.users.picker.headers.system"
),
@@ -87,8 +106,9 @@ export class HaConfigUsers extends LitElement {
width: "160px",
template: (generated) =>
generated ? html`<ha-icon icon="hass:check"> </ha-icon>` : "",
};
}
},
};
return columns;
}
);

View File

@@ -84,9 +84,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
}
public setConfig(config: ButtonCardConfig): void {
if (!config.entity) {
throw new Error("Entity must be specified");
}
if (config.entity && !isValidEntityId(config.entity)) {
throw new Error("Invalid entity");
}

View File

@@ -109,7 +109,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
}
public setConfig(config: EntitiesCardConfig): void {
if (!config || !config.entities.length) {
if (!config.entities || !Array.isArray(config.entities)) {
throw new Error("Entities must be specified");
}

View File

@@ -2,7 +2,14 @@ import { customElement } from "lit-element";
import { HuiButtonCard } from "./hui-button-card";
@customElement("hui-entity-button-card")
class HuiEntityButtonCard extends HuiButtonCard {}
class HuiEntityButtonCard extends HuiButtonCard {
public setConfig(config): void {
if (!config.entity) {
throw new Error("Entity must be specified");
}
super.setConfig(config);
}
}
declare global {
interface HTMLElementTagNameMap {

View File

@@ -1,16 +1,16 @@
import { safeDump } from "js-yaml";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
internalProperty,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { LovelaceCard } from "../types";
import { ErrorCardConfig } from "./types";
import { safeDump } from "js-yaml";
@customElement("hui-error-card")
export class HuiErrorCard extends LitElement implements LovelaceCard {
@@ -49,7 +49,6 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
font-weight: 500;
user-select: text;
cursor: default;
overflow: hidden;
}
pre {
font-family: var(--code-font-family, monospace);

View File

@@ -16,7 +16,6 @@ import "../../../components/state-history-charts";
import { CacheConfig, getRecentWithCache } from "../../../data/cached-history";
import { HistoryResult } from "../../../data/history";
import { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entites";
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
import { processConfigEntities } from "../common/process-config-entities";
import { EntityConfig } from "../entity-rows/types";
@@ -30,22 +29,9 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
return document.createElement("hui-history-graph-card-editor");
}
public static getStubConfig(
hass: HomeAssistant,
entities: string[],
entitiesFallback: string[]
): HistoryGraphCardConfig {
const includeDomains = ["sensor"];
const maxEntities = 1;
const foundEntities = findEntities(
hass,
maxEntities,
entities,
entitiesFallback,
includeDomains
);
return { type: "history-graph", entities: foundEntities };
public static getStubConfig(): HistoryGraphCardConfig {
// Hard coded to sun.sun to prevent high server load when it would pick an entity with a lot of state changes
return { type: "history-graph", entities: ["sun.sun"] };
}
@property({ attribute: false }) public hass?: HomeAssistant;
@@ -71,12 +57,12 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
}
public setConfig(config: HistoryGraphCardConfig): void {
if (!config.entities.length) {
throw new Error("Entities must be specified");
if (!config.entities || !Array.isArray(config.entities)) {
throw new Error("Entities need to be an array");
}
if (config.entities && !Array.isArray(config.entities)) {
throw new Error("Entities need to be an array");
if (!config.entities.length) {
throw new Error("You must include at least one entity");
}
this._config = config;

View File

@@ -29,7 +29,7 @@ class HuiHorizontalStackCard extends HuiStackCard {
}
#root > * {
flex: 1 1 0;
margin: 0 4px;
margin: var(--horizontal-stack-card-margin, var(--stack-card-margin, 0 4px));
min-width: 0;
}
#root > *:first-child {

View File

@@ -246,78 +246,73 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
${!isUnavailable &&
(mediaDescription || stateObj.attributes.media_title || showControls)
? html`
<div
class="title-controls"
style=${styleMap({
paddingRight: isOffState
? "0"
: `${this._cardHeight - 40}px`,
})}
>
${!mediaDescription && !stateObj.attributes.media_title
? ""
: html`
<div class="media-info">
<hui-marquee
.text=${stateObj.attributes.media_title ||
mediaDescription}
.active=${this._marqueeActive}
@mouseover=${this._marqueeMouseOver}
@mouseleave=${this._marqueeMouseLeave}
></hui-marquee>
${!stateObj.attributes.media_title
? ""
: mediaDescription}
</div>
`}
${!showControls
? ""
: html`
<div class="controls">
${controls!.map(
(control) => html`
<ha-icon-button
.title=${this.hass.localize(
`ui.card.media_player.${control.action}`
)}
.icon=${control.icon}
action=${control.action}
@click=${this._handleClick}
></ha-icon-button>
`
)}
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
? html`
<mwc-icon-button
class="browse-media"
<div>
<div class="title-controls">
${!mediaDescription && !stateObj.attributes.media_title
? ""
: html`
<div class="media-info">
<hui-marquee
.text=${stateObj.attributes.media_title ||
mediaDescription}
.active=${this._marqueeActive}
@mouseover=${this._marqueeMouseOver}
@mouseleave=${this._marqueeMouseLeave}
></hui-marquee>
${!stateObj.attributes.media_title
? ""
: mediaDescription}
</div>
`}
${!showControls
? ""
: html`
<div class="controls">
${controls!.map(
(control) => html`
<ha-icon-button
.title=${this.hass.localize(
"ui.card.media_player.browse_media"
`ui.card.media_player.${control.action}`
)}
@click=${this._handleBrowseMedia}
><ha-svg-icon
.path=${mdiPlayBoxMultiple}
></ha-svg-icon
></mwc-icon-button>
.icon=${control.icon}
action=${control.action}
@click=${this._handleClick}
></ha-icon-button>
`
: ""}
</div>
)}
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
? html`
<mwc-icon-button
class="browse-media"
.title=${this.hass.localize(
"ui.card.media_player.browse_media"
)}
@click=${this._handleBrowseMedia}
><ha-svg-icon
.path=${mdiPlayBoxMultiple}
></ha-svg-icon
></mwc-icon-button>
`
: ""}
</div>
`}
</div>
${!this._showProgressBar
? ""
: html`
<paper-progress
.max=${stateObj.attributes.media_duration}
style=${styleMap({
"--paper-progress-active-color":
this._foregroundColor || "var(--accent-color)",
cursor: supportsFeature(stateObj, SUPPORT_SEEK)
? "pointer"
: "initial",
})}
@click=${this._handleSeek}
></paper-progress>
`}
</div>
${!this._showProgressBar
? ""
: html`
<paper-progress
.max=${stateObj.attributes.media_duration}
style=${styleMap({
"--paper-progress-active-color":
this._foregroundColor || "var(--accent-color)",
cursor: supportsFeature(stateObj, SUPPORT_SEEK)
? "pointer"
: "initial",
})}
@click=${this._handleSeek}
></paper-progress>
`}
`
: ""}
</div>
@@ -635,6 +630,11 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
.player {
position: relative;
padding: 16px;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
color: var(--text-primary-color);
transition-property: color, padding;
transition-duration: 0.4s;
@@ -671,7 +671,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
mwc-icon-button.browse-media {
position: absolute;
right: 0;
right: 4px;
--mdc-icon-size: 24px;
}
@@ -693,7 +693,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
.more-info {
position: absolute;
top: 4px;
right: 0px;
right: 4px;
}
.media-info {

View File

@@ -29,7 +29,7 @@ class HuiVerticalStackCard extends HuiStackCard {
height: 100%;
}
#root > * {
margin: 4px 0 4px 0;
margin: var(--vertical-stack-card-margin, var(--stack-card-margin, 4px 0));
}
#root > *:first-child {
margin-top: 0;

View File

@@ -179,9 +179,6 @@ export class HuiActionEditor extends LitElement {
.dropdown {
display: flex;
}
paper-dropdown-menu {
flex-grow: 1;
}
`;
}
}

View File

@@ -1,7 +1,8 @@
import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-icon-button";
import "../../../components/ha-button-menu";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
import {
css,
CSSResult,
@@ -9,21 +10,20 @@ import {
html,
LitElement,
property,
TemplateResult,
queryAssignedNodes,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
import { swapCard, moveCard, addCard, deleteCard } from "../editor/config-util";
import { confDeleteCard } from "../editor/delete-card";
import { Lovelace, LovelaceCard } from "../types";
import { computeCardSize } from "../common/compute-card-size";
import { mdiDotsVertical, mdiArrowDown, mdiArrowUp } from "@mdi/js";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import { showSelectViewDialog } from "../editor/select-view/show-select-view-dialog";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-button-menu";
import { saveConfig } from "../../../data/lovelace";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../types";
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
import { computeCardSize } from "../common/compute-card-size";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
import { addCard, deleteCard, moveCard, swapCard } from "../editor/config-util";
import { showSelectViewDialog } from "../editor/select-view/show-select-view-dialog";
import { Lovelace, LovelaceCard } from "../types";
@customElement("hui-card-options")
export class HuiCardOptions extends LitElement {
@@ -168,11 +168,7 @@ export class HuiCardOptions extends LitElement {
}
private _editCard(): void {
showEditCardDialog(this, {
lovelaceConfig: this.lovelace!.config,
saveConfig: this.lovelace!.saveConfig,
path: this.path!,
});
fireEvent(this, "ll-edit-card", { path: this.path! });
}
private _cardUp(): void {
@@ -229,7 +225,7 @@ export class HuiCardOptions extends LitElement {
}
private _deleteCard(): void {
confDeleteCard(this, this.hass!, this.lovelace!, this.path!);
fireEvent(this, "ll-delete-card", { path: this.path! });
}
}

View File

@@ -1,4 +1,4 @@
import { mdiDragVerticalVariant } from "@mdi/js";
import { mdiDrag } from "@mdi/js";
import {
css,
CSSResult,
@@ -68,7 +68,7 @@ export class HuiEntityEditor extends LitElement {
: this.entities!.map((entityConf, index) => {
return html`
<div class="entity" data-entity-id=${entityConf.entity}>
<ha-svg-icon .path=${mdiDragVerticalVariant}></ha-svg-icon>
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
<ha-entity-picker
.hass=${this.hass}
.value=${entityConf.entity}
@@ -192,7 +192,6 @@ export class HuiEntityEditor extends LitElement {
.entity ha-svg-icon {
padding-right: 8px;
cursor: move;
color: var(--secondary-text-color);
}
.entity ha-entity-picker {
flex-grow: 1;

View File

@@ -72,7 +72,7 @@ class HuiMarquee extends LitElement {
display: flex;
position: relative;
align-items: center;
height: 1em;
height: 1.2em;
contain: strict;
}

View File

@@ -26,7 +26,11 @@ export class HuiThemeSelectEditor extends LitElement {
return html`
<paper-dropdown-menu
.label=${this.label ||
this.hass!.localize("ui.panel.lovelace.editor.card.generic.theme")}
`${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.theme"
)} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})`}
dynamic-align
>
<paper-listbox
@@ -55,6 +59,9 @@ export class HuiThemeSelectEditor extends LitElement {
paper-dropdown-menu {
width: 100%;
}
paper-item {
cursor: pointer;
}
`;
}

View File

@@ -353,11 +353,9 @@ export class HuiCardPicker extends LitElement {
max-width: 500px;
display: flex;
flex-direction: column;
border-radius: 4px;
border: 1px solid var(--divider-color);
border-radius: var(--ha-card-border-radius, 4px);
background: var(--primary-background-color, #fafafa);
cursor: pointer;
box-sizing: border-box;
position: relative;
}
@@ -375,7 +373,6 @@ export class HuiCardPicker extends LitElement {
--ha-card-background,
var(--card-background-color, white)
);
border-radius: 0 0 4px 4px;
border-bottom: 1px solid var(--divider-color);
}
@@ -408,6 +405,10 @@ export class HuiCardPicker extends LitElement {
width: 100%;
height: 100%;
z-index: 1;
box-sizing: border-box;
border: var(--ha-card-border-width, 1px) solid
var(--ha-card-border-color, var(--divider-color));
border-radius: var(--ha-card-border-radius, 4px);
}
.manual {

View File

@@ -1,14 +1,5 @@
import { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import {
mdiCheckboxBlankOutline,
mdiCheckBoxOutline,
mdiCodeBracesBox,
mdiDotsVertical,
mdiFormSelect,
mdiHelpCircleOutline,
} from "@mdi/js";
import { mdiHelpCircle } from "@mdi/js";
import deepFreeze from "deep-freeze";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResultArray,
@@ -23,16 +14,10 @@ import {
} from "lit-element";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
import { shouldHandleRequestSelectedEvent } from "../../../../common/mwc/handle-request-selected-event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-circular-progress";
import "../../../../components/ha-dialog";
import "../../../../components/ha-header-bar";
import "../../../../components/ha-menu-button";
import {
CoreFrontendUserData,
getOptimisticFrontendUserDataCollection,
} from "../../../../data/frontend";
import type {
LovelaceCardConfig,
LovelaceViewConfig,
@@ -55,7 +40,6 @@ declare global {
// for fire event
interface HASSDomEvents {
"reload-lovelace": undefined;
"scroll-to-pos": { x: number; y: number };
}
// for add event listener
interface HTMLElementEventMap {
@@ -91,12 +75,6 @@ export class HuiDialogEditCard extends LitElement
@internalProperty() private _dirty = false;
@internalProperty() private _coreUserData?: CoreFrontendUserData | null;
@internalProperty() private _isAdvanced? = false;
private _unsubCoreData?: UnsubscribeFunc;
public async showDialog(params: EditCardDialogParams): Promise<void> {
this._params = params;
this._GUImode = true;
@@ -112,13 +90,6 @@ export class HuiDialogEditCard extends LitElement
if (params.cardConfig) {
this._dirty = true;
}
this._unsubCoreData = getOptimisticFrontendUserDataCollection(
this.hass.connection,
"core"
).subscribe((coreUserData) => {
this._coreUserData = coreUserData;
this._isAdvanced = coreUserData?.showAdvanced;
});
}
public closeDialog(): boolean {
@@ -131,10 +102,6 @@ export class HuiDialogEditCard extends LitElement
this._error = undefined;
this._documentationURL = undefined;
this._dirty = false;
if (this._unsubCoreData) {
this._unsubCoreData();
this._unsubCoreData = undefined;
}
fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
}
@@ -190,11 +157,10 @@ export class HuiDialogEditCard extends LitElement
<ha-dialog
open
scrimClickAction
full
.heading=${true}
@keydown=${this._ignoreKeydown}
@closed=${this._cancel}
@opened=${this._opened}
.heading=${true}
>
<div slot="heading">
<ha-header-bar>
@@ -211,75 +177,11 @@ export class HuiDialogEditCard extends LitElement
dir=${computeRTLDirection(this.hass)}
>
<mwc-icon-button>
<ha-svg-icon .path=${mdiHelpCircleOutline}></ha-svg-icon>
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
</mwc-icon-button>
</a>
`
: ""}
${this._cardConfig !== undefined
? html`
<ha-button-menu
fixed
corner="BOTTOM_START"
slot="actionItems"
@closed=${(ev) => ev.stopPropagation()}
>
<mwc-icon-button
slot="trigger"
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.menu.open"
)}
.title=${this.hass!.localize(
"ui.panel.lovelace.editor.menu.open"
)}
>
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
${this._coreUserData?.showAdvanced === true
? html`
<mwc-list-item
graphic="icon"
.label=${this.hass!.localize(
!this._cardEditorEl || this._GUImode
? "ui.panel.lovelace.editor.edit_card.show_code_editor"
: "ui.panel.lovelace.editor.edit_card.show_visual_editor"
)}
.disabled=${!this._guiModeAvailable}
@request-selected=${this._toggleMode}
>
<span
>${this.hass!.localize(
!this._cardEditorEl || this._GUImode
? "ui.panel.lovelace.editor.edit_card.show_code_editor"
: "ui.panel.lovelace.editor.edit_card.show_visual_editor"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${!this._cardEditorEl || this._GUImode
? mdiCodeBracesBox
: mdiFormSelect}
></ha-svg-icon>
</mwc-list-item>
`
: html`
<mwc-list-item
graphic="icon"
label="Show Advanced Options"
@request-selected=${this._toggleAdvanced}
>
<span>Show Advanced Options</span>
<ha-svg-icon
slot="graphic"
.path=${this._isAdvanced
? mdiCheckBoxOutline
: mdiCheckboxBlankOutline}
></ha-svg-icon>
</mwc-list-item>
`}
</ha-button-menu>
`
: ""}
</ha-header-bar>
</div>
<div class="content">
@@ -288,11 +190,9 @@ export class HuiDialogEditCard extends LitElement
.hass=${this.hass}
.lovelace=${this._params.lovelaceConfig}
.value=${this._cardConfig}
.isAdvanced=${this._isAdvanced}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
@editor-save=${this._save}
@scroll-to-pos=${this._scrollToPos}
></hui-card-element-editor>
</div>
<div class="element-preview">
@@ -311,6 +211,22 @@ export class HuiDialogEditCard extends LitElement
: ``}
</div>
</div>
${this._cardConfig !== undefined
? html`
<mwc-button
slot="secondaryAction"
@click=${this._toggleMode}
.disabled=${!this._guiModeAvailable}
class="gui-mode-button"
>
${this.hass!.localize(
!this._cardEditorEl || this._GUImode
? "ui.panel.lovelace.editor.edit_card.show_code_editor"
: "ui.panel.lovelace.editor.edit_card.show_visual_editor"
)}
</mwc-button>
`
: ""}
<div slot="primaryAction" @click=${this._save}>
<mwc-button @click=${this._cancel}>
${this.hass!.localize("ui.common.cancel")}
@@ -348,13 +264,6 @@ export class HuiDialogEditCard extends LitElement
ev.stopPropagation();
}
private _scrollToPos(ev: CustomEvent): void {
this.shadowRoot!.querySelector("ha-dialog")?.scrollToPos(
ev.detail.x,
ev.detail.y
);
}
private _handleConfigChanged(ev: HASSDomEvent<ConfigChangedEvent>) {
this._cardConfig = deepFreeze(ev.detail.config);
this._error = ev.detail.error;
@@ -368,20 +277,10 @@ export class HuiDialogEditCard extends LitElement
this._guiModeAvailable = ev.detail.guiModeAvailable;
}
private _toggleMode(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
private _toggleMode(): void {
this._cardEditorEl?.toggleMode();
}
private _toggleAdvanced(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this._isAdvanced = !this._isAdvanced;
}
private _opened() {
this._cardEditorEl?.refreshYamlEditor();
}
@@ -459,45 +358,35 @@ export class HuiDialogEditCard extends LitElement
css`
:host {
--code-mirror-max-height: calc(100vh - 176px);
--dialog-content-padding: 0px 24px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
/* overrule the ha-style-dialog max-height on small screens */
ha-dialog {
--mdc-dialog-max-height: 100%;
}
}
@media all and (min-width: 451px) and (min-height: 501px) {
ha-dialog {
--mdc-dialog-max-width: 90vw;
}
:host([large]) ha-dialog {
width: calc(90vw - 48px);
height: 100%;
}
}
@media all and (min-width: 850px) {
ha-dialog {
--mdc-dialog-min-width: 845px;
}
}
@media (min-width: 1200px) {
ha-dialog {
--mdc-dialog-max-width: 1250px;
--dialog-surface-position: fixed;
--mdc-dialog-max-height: calc(100% - 72px);
}
}
ha-dialog {
--mdc-dialog-max-width: 845px;
--dialog-z-index: 5;
--mdc-dialog-max-height: 750px;
}
:host([large]) ha-dialog {
--mdc-dialog-max-width: calc(100% - 32px);
@media all and (min-width: 451px) and (min-height: 501px) {
ha-dialog {
--mdc-dialog-max-width: 90vw;
}
:host([large]) .content {
width: calc(90vw - 48px);
}
}
ha-header-bar {
@@ -518,17 +407,23 @@ export class HuiDialogEditCard extends LitElement
flex-direction: column;
margin: 0 -10px;
}
.content hui-card-preview {
margin: 4px auto;
max-width: 390px;
}
.content .element-editor {
margin: 0 10px;
}
@media (min-width: 1200px) {
ha-dialog {
--mdc-dialog-max-width: calc(100% - 32px);
--mdc-dialog-min-width: 1000px;
--dialog-surface-position: fixed;
--dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
}
.content {
flex-direction: row;
}
@@ -540,34 +435,32 @@ export class HuiDialogEditCard extends LitElement
}
.content hui-card-preview {
padding: 8px 10px;
margin: auto;
margin: auto 0px;
max-width: 500px;
}
}
.hidden {
display: none;
}
.element-editor {
margin-bottom: 8px;
}
.blur {
filter: blur(2px) grayscale(100%);
}
.element-preview {
position: relative;
height: max-content;
background: var(--primary-background-color);
padding: 4px;
border-radius: 4px;
}
.element-preview ha-circular-progress {
top: 50%;
left: 50%;
position: absolute;
z-index: 10;
}
hui-card-preview {
padding-top: 8px;
margin-bottom: 4px;
@@ -575,13 +468,14 @@ export class HuiDialogEditCard extends LitElement
width: 100%;
box-sizing: border-box;
}
.gui-mode-button {
margin-right: auto;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header_button {
color: inherit;
text-decoration: none;

View File

@@ -8,7 +8,7 @@ export interface CreateCardDialogParams {
entities?: string[]; // We can pass entity id's that will be added to the config when a card is picked
}
const importCreateCardDialog = () => import("./hui-dialog-create-card");
export const importCreateCardDialog = () => import("./hui-dialog-create-card");
export const showCreateCardDialog = (
element: HTMLElement,

View File

@@ -6,7 +6,7 @@ export interface DeleteCardDialogParams {
cardConfig?: LovelaceCardConfig;
}
const importDeleteCardDialog = () => import("./hui-dialog-delete-card");
export const importDeleteCardDialog = () => import("./hui-dialog-delete-card");
export const showDeleteCardDialog = (
element: HTMLElement,

View File

@@ -8,7 +8,7 @@ export interface EditCardDialogParams {
cardConfig?: LovelaceCardConfig;
}
const importEditCardDialog = () => import("./hui-dialog-edit-card");
export const importEditCardDialog = () => import("./hui-dialog-edit-card");
export const showEditCardDialog = (
element: HTMLElement,

View File

@@ -14,13 +14,4 @@ export const configElementStyle = css`
.suffix {
margin: 0 8px;
}
ha-settings-row {
padding: 0;
}
ha-expansion-panel {
padding-top: 8px;
}
ha-expansion-panel .title {
font-size: 16px;
}
`;

View File

@@ -19,7 +19,6 @@ import { HomeAssistant } from "../../../../types";
import { AlarmPanelCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -38,8 +37,6 @@ export class HuiAlarmPanelCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: AlarmPanelCardConfig;
public setConfig(config: AlarmPanelCardConfig): void {
@@ -71,68 +68,62 @@ export class HuiAlarmPanelCardEditor extends LitElement
const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"];
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
allow-custom-entity
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
<span>Used States</span>
${this._states.map((state, index) => {
return html`
<div class="states">
<paper-item>${state}</paper-item>
<ha-icon
class="deleteState"
.value=${index}
icon="hass:close"
@click=${this._stateRemoved}
></ha-icon>
</div>
`;
})}
<paper-dropdown-menu
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.alarm-panel.available_states"
)}
@value-changed=${this._stateAdded}
>
<paper-listbox slot="dropdown-content">
${states.map((state) => {
return html`<paper-item>${state}</paper-item>`;
})}
</paper-listbox>
</paper-dropdown-menu>
</div>
<div slot="advanced" class="card-config">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value="${this._entity}"
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._name}"
.configValue="${"name"}"
@value-changed="${this._valueChanged}"
></paper-input>
<span>Used States</span> ${this._states.map((state, index) => {
return html`
<div class="states">
<paper-item>${state}</paper-item>
<ha-icon
class="deleteState"
.value="${index}"
icon="hass:close"
@click=${this._stateRemoved}
></ha-icon>
</div>
`;
})}
<paper-dropdown-menu
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.alarm-panel.available_states"
)}"
@value-changed="${this._stateAdded}"
>
<paper-listbox slot="dropdown-content">
${states.map((state) => {
return html` <paper-item>${state}</paper-item> `;
})}
</paper-listbox>
</paper-dropdown-menu>
<hui-theme-select-editor
.hass=${this.hass}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
`;
}

View File

@@ -11,9 +11,9 @@ import {
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-settings-row";
import "../../../../components/ha-switch";
import { ActionConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types";
@@ -22,7 +22,6 @@ import "../../components/hui-action-editor";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { actionConfigStruct, EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -54,8 +53,6 @@ export class HuiButtonCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: ButtonCardConfig;
public setConfig(config: ButtonCardConfig): void {
@@ -110,34 +107,117 @@ export class HuiButtonCardEditor extends LitElement
return html``;
}
const dir = computeRTLDirection(this.hass!);
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
allow-custom-entity
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)}
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
@value-changed=${this._valueChanged}
></ha-entity-picker>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
@value-changed=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<div class="side-by-side">
<paper-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-icon-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.icon"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._icon}
.placeholder=${this._icon ||
stateIcon(this.hass.states[this._entity])}
.configValue=${"icon"}
@value-changed=${this._valueChanged}
></ha-icon-input>
</div>
<div class="side-by-side">
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_name"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._show_name !== false}
.configValue=${"show_name"}
@change=${this._change}
></ha-switch>
</ha-formfield>
</div>
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_state"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._show_state !== false}
.configValue=${"show_state"}
@change=${this._change}
></ha-switch>
</ha-formfield>
</div>
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_icon"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._show_icon !== false}
.configValue=${"show_icon"}
@change=${this._change}
></ha-switch>
</ha-formfield>
</div>
</div>
<div class="side-by-side">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.icon_height"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._icon_height}
.configValue=${"icon_height"}
@value-changed=${this._valueChanged}
type="number"
><div class="suffix" slot="suffix">px</div>
</paper-input>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
<div class="side-by-side">
<hui-action-editor
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.tap_action"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config=${this._tap_action}
.actions=${actions}
@@ -147,40 +227,12 @@ export class HuiButtonCardEditor extends LitElement
)}
@value-changed=${this._valueChanged}
></hui-action-editor>
</div>
<div slot="advanced" class="card-config">
<div class="side-by-side">
<ha-icon-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.icon"
)}
.value=${this._icon}
.placeholder=${this._icon ||
stateIcon(this.hass.states[this._entity])}
.configValue=${"icon"}
@value-changed=${this._valueChanged}
></ha-icon-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.icon_height"
)}
.value=${this._icon_height}
.configValue=${"icon_height"}
@value-changed=${this._valueChanged}
type="number"
><div class="suffix" slot="suffix">px</div>
</paper-input>
</div>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
<hui-action-editor
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hold_action"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config=${this._hold_action}
.actions=${actions}
@@ -190,44 +242,8 @@ export class HuiButtonCardEditor extends LitElement
)}
@value-changed=${this._valueChanged}
></hui-action-editor>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_state"
)}
</span>
<ha-switch
.checked=${this._show_state !== false}
.configValue=${"show_state"}
@change=${this._change}
></ha-switch>
</ha-settings-row>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_name"
)}
</span>
<ha-switch
.checked=${this._show_name !== false}
.configValue=${"show_name"}
@change=${this._change}
></ha-switch>
</ha-settings-row>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_icon"
)}
</span>
<ha-switch
.checked=${this._show_icon !== false}
.configValue=${"show_icon"}
@change=${this._change}
></ha-switch>
</ha-settings-row>
</div>
</hui-config-element-template>
</div>
`;
}

View File

@@ -1,6 +1,5 @@
import {
css,
CSSResultArray,
CSSResult,
customElement,
html,
internalProperty,
@@ -24,7 +23,6 @@ import type { CalendarCardConfig } from "../../cards/types";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import type { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import type { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -43,8 +41,6 @@ export class HuiCalendarCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@property({ attribute: false }) private _config?: CalendarCardConfig;
@internalProperty() private _configEntities?: string[];
@@ -73,15 +69,14 @@ export class HuiCalendarCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<div class="card-config">
<div class="side-by-side">
<paper-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
@@ -110,30 +105,28 @@ export class HuiCalendarCardEditor extends LitElement
</paper-listbox>
</paper-dropdown-menu>
</div>
<h3>
${`${this.hass.localize(
"ui.panel.lovelace.editor.card.calendar.calendar_entities"
)} (
${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.required"
)})`}
</h3>
<ha-entities-picker
.hass=${this.hass!}
.value=${this._configEntities}
.includeDomains=${["calendar"]}
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
>
</ha-entities-picker>
<div slot="advanced" class="card-config">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
></hui-theme-select-editor>
</div>
<h3>
${this.hass.localize(
"ui.panel.lovelace.editor.card.calendar.calendar_entities"
) +
" (" +
this.hass!.localize("ui.panel.lovelace.editor.card.config.required") +
")"}
</h3>
<ha-entities-picker
.hass=${this.hass!}
.value=${this._configEntities}
.includeDomains=${["calendar"]}
@value-changed=${this._valueChanged}
>
</ha-entities-picker>
`;
}
@@ -182,15 +175,8 @@ export class HuiCalendarCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResultArray {
return [
configElementStyle,
css`
paper-dropdown-menu {
width: 100%;
}
`,
];
static get styles(): CSSResult {
return configElementStyle;
}
}

View File

@@ -22,11 +22,11 @@ import {
union,
} from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-card";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon";
import "../../../../components/ha-settings-row";
import "../../../../components/ha-switch";
import type { HomeAssistant } from "../../../../types";
import type { EntitiesCardConfig } from "../../cards/types";
@@ -35,7 +35,6 @@ import type { LovelaceRowConfig } from "../../entity-rows/types";
import { headerFooterConfigStructs } from "../../header-footer/types";
import type { LovelaceCardEditor } from "../../types";
import "../header-footer-editor/hui-header-footer-editor";
import "../hui-config-element-template";
import "../hui-entities-card-row-editor";
import "../hui-sub-element-editor";
import { processEditorEntities } from "../process-editor-entities";
@@ -63,8 +62,6 @@ export class HuiEntitiesCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: EntitiesCardConfig;
@internalProperty() private _configEntities?: LovelaceRowConfig[];
@@ -95,7 +92,6 @@ export class HuiEntitiesCardEditor extends LitElement
<hui-sub-element-editor
.hass=${this.hass}
.config=${this._subElementEditorConfig}
.isAdvancedUser=${this.isAdvanced}
@go-back=${this._goBack}
@config-changed=${this._handleSubElementChanged}
>
@@ -104,80 +100,70 @@ export class HuiEntitiesCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-settings-row>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.entities.show_header_toggle"
)}
</span>
<span slot="description">
${this.hass.localize(
"ui.panel.lovelace.editor.card.entities.show_header_toggle_secondary"
)}
</span>
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
<div class="side-by-side">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.entities.show_header_toggle"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${this._config!.show_header_toggle !== false}
.configValue=${"show_header_toggle"}
@change=${this._valueChanged}
></ha-switch>
</ha-settings-row>
</div>
<hui-entities-card-row-editor
.hass=${this.hass}
.entities=${this._configEntities}
@entities-changed=${this._valueChanged}
@edit-detail-element=${this._editDetailElement}
></hui-entities-card-row-editor>
<div slot="advanced" class="card-config">
<hui-header-footer-editor
.hass=${this.hass}
.configValue=${"header"}
.config=${this._config.header}
@value-changed=${this._valueChanged}
@edit-detail-element=${this._editDetailElement}
></hui-header-footer-editor>
<hui-header-footer-editor
.hass=${this.hass}
.configValue=${"footer"}
.config=${this._config.footer}
@value-changed=${this._valueChanged}
@edit-detail-element=${this._editDetailElement}
></hui-header-footer-editor>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.state_color"
)}
</span>
</ha-formfield>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.state_color"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${this._config!.state_color}
.configValue=${"state_color"}
@change=${this._valueChanged}
></ha-switch>
</ha-settings-row>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</ha-formfield>
</div>
</hui-config-element-template>
<hui-header-footer-editor
.hass=${this.hass}
.configValue=${"header"}
.config=${this._config.header}
@value-changed=${this._valueChanged}
@edit-detail-element=${this._editDetailElement}
></hui-header-footer-editor>
<hui-header-footer-editor
.hass=${this.hass}
.configValue=${"footer"}
.config=${this._config.footer}
@value-changed=${this._valueChanged}
@edit-detail-element=${this._editDetailElement}
></hui-header-footer-editor>
</div>
<hui-entities-card-row-editor
.hass=${this.hass}
.entities=${this._configEntities}
@entities-changed=${this._valueChanged}
@edit-detail-element=${this._editDetailElement}
></hui-entities-card-row-editor>
`;
}
@@ -202,9 +188,20 @@ export class HuiEntitiesCardEditor extends LitElement
return;
}
if (ev.detail && ev.detail.entities) {
if (configValue === "row" || (ev.detail && ev.detail.entities)) {
const newConfigEntities =
ev.detail.entities || this._configEntities!.concat();
if (configValue === "row") {
if (!value) {
newConfigEntities.splice(this._subElementEditorConfig!.index!, 1);
this._goBack();
} else {
newConfigEntities[this._subElementEditorConfig!.index!] = value;
}
this._subElementEditorConfig!.elementConfig = value;
}
this._config = { ...this._config!, entities: newConfigEntities };
this._configEntities = processEditorEntities(this._config!.entities);
} else if (configValue) {
@@ -224,20 +221,18 @@ export class HuiEntitiesCardEditor extends LitElement
private _handleSubElementChanged(ev: CustomEvent): void {
ev.stopPropagation();
const configValue = this._subElementEditorConfig?.type;
if (!this._config || !this.hass || !configValue) {
if (!this._config || !this.hass) {
return;
}
const configValue = this._subElementEditorConfig?.type;
const value = ev.detail.config;
let goBack = false;
if (configValue === "row") {
const newConfigEntities = this._configEntities!.concat();
if (!value) {
newConfigEntities.splice(this._subElementEditorConfig!.index!, 1);
goBack = true;
this._goBack();
} else {
newConfigEntities[this._subElementEditorConfig!.index!] = value;
}
@@ -245,10 +240,9 @@ export class HuiEntitiesCardEditor extends LitElement
this._config = { ...this._config!, entities: newConfigEntities };
this._configEntities = processEditorEntities(this._config!.entities);
} else if (configValue) {
if (!value || value === "") {
if (value === "") {
this._config = { ...this._config };
delete this._config[configValue!];
goBack = true;
} else {
this._config = {
...this._config,
@@ -257,21 +251,16 @@ export class HuiEntitiesCardEditor extends LitElement
}
}
if (goBack) {
this._goBack();
} else {
this._subElementEditorConfig = {
...this._subElementEditorConfig!,
elementConfig: value,
};
}
this._subElementEditorConfig = {
...this._subElementEditorConfig!,
elementConfig: value,
};
fireEvent(this, "config-changed", { config: this._config });
}
private _editDetailElement(ev: HASSDomEvent<EditSubElementEvent>): void {
this._subElementEditorConfig = ev.detail.subElementConfig;
fireEvent(this, "scroll-to-pos", { x: 0, y: 0 });
}
private _goBack(): void {

View File

@@ -20,7 +20,6 @@ import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { headerFooterConfigStructs } from "../../header-footer/types";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -40,8 +39,6 @@ export class HuiEntityCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: EntityCardConfig;
public setConfig(config: EntityCardConfig): void {
@@ -79,67 +76,74 @@ export class HuiEntityCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)}
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<div class="side-by-side">
<paper-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<div slot="advanced" class="card-config">
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${this._entity}
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.attribute"
)}
.value=${this._attribute}
.configValue=${"attribute"}
@value-changed=${this._valueChanged}
></ha-entity-attribute-picker>
<ha-icon-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.icon"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._icon}
.placeholder=${this._icon ||
stateIcon(this.hass.states[this._entity])}
.configValue=${"icon"}
@value-changed=${this._valueChanged}
></ha-icon-input>
</div>
<div class="side-by-side">
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${this._entity}
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.attribute"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._attribute}
.configValue=${"attribute"}
@value-changed=${this._valueChanged}
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.unit"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._unit}
.configValue=${"unit"}
@value-changed=${this._valueChanged}
></paper-input>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
`;
}

View File

@@ -1,6 +1,7 @@
import "@polymer/paper-input/paper-input";
import {
CSSResult,
css,
CSSResultArray,
customElement,
html,
internalProperty,
@@ -10,15 +11,14 @@ import {
} from "lit-element";
import { assert, number, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-formfield";
import "../../../../components/ha-settings-row";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types";
import { GaugeCardConfig, SeverityConfig } from "../../cards/types";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -40,8 +40,6 @@ export class HuiGaugeCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: GaugeCardConfig;
public setConfig(config: GaugeCardConfig): void {
@@ -83,131 +81,141 @@ export class HuiGaugeCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
allow-custom-entity
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value="${this._entity}"
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._name}"
.configValue=${"name"}
@value-changed="${this._valueChanged}"
></paper-input>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.unit"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._unit}"
.configValue=${"unit"}
@value-changed="${this._valueChanged}"
></paper-input>
<hui-theme-select-editor
.hass=${this.hass}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.minimum"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._min}"
.configValue=${"min"}
@value-changed="${this._valueChanged}"
></paper-input>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.maximum"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._max}"
.configValue=${"max"}
@value-changed="${this._valueChanged}"
></paper-input>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.gauge.severity.define"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked="${this._config!.severity !== undefined}"
@change="${this._toggleSeverity}"
></ha-switch
></ha-formfield>
${this._config!.severity !== undefined
? html`
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.gauge.severity.green"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="side-by-side">
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.minimum"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._min}
.configValue=${"min"}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.maximum"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._max}
.configValue=${"max"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
</div>
<div slot="advanced" class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.unit"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
.value="${this._severity ? this._severity.green : 0}"
.configValue=${"green"}
@value-changed="${this._severityChanged}"
></paper-input>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.gauge.severity.yellow"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value=${this._unit}
.configValue=${"unit"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.gauge.severity.define"
)}
</span>
<ha-switch
.checked=${this._config!.severity !== undefined}
@change=${this._toggleSeverity}
></ha-switch>
</ha-settings-row>
${this._config!.severity !== undefined
? html`
<div class="side-by-side">
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.gauge.severity.green"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value=${this._severity ? this._severity.green : 0}
.configValue=${"green"}
@value-changed=${this._severityChanged}
></paper-input>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.gauge.severity.yellow"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value=${this._severity ? this._severity.yellow : 0}
.configValue=${"yellow"}
@value-changed=${this._severityChanged}
></paper-input>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.gauge.severity.red"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value=${this._severity ? this._severity.red : 0}
.configValue=${"red"}
@value-changed=${this._severityChanged}
></paper-input>
</div>
`
: ""}
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
.value="${this._severity ? this._severity.yellow : 0}"
.configValue=${"yellow"}
@value-changed="${this._severityChanged}"
></paper-input>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.gauge.severity.red"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value="${this._severity ? this._severity.red : 0}"
.configValue=${"red"}
@value-changed="${this._severityChanged}"
></paper-input>
</div>
`
: ""}
</div>
`;
}
static get styles(): CSSResult {
return configElementStyle;
static get styles(): CSSResultArray {
return [
configElementStyle,
css`
.severity {
display: none;
width: 100%;
padding-left: 16px;
flex-direction: row;
flex-wrap: wrap;
}
.severity > * {
flex: 1 0 30%;
padding-right: 4px;
}
ha-switch[checked] ~ .severity {
display: flex;
}
`,
];
}
private _toggleSeverity(ev: EntitiesEditorEvent): void {

View File

@@ -76,7 +76,6 @@ export class HuiGenericEntityRowEditor extends LitElement
return html`
<div class="card-config">
<ha-entity-picker
hide-clear-icon
allow-custom-entity
.hass=${this.hass}
.value=${this._config.entity}

View File

@@ -21,18 +21,17 @@ import {
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-card";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon";
import "../../../../components/ha-settings-row";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types";
import { ConfigEntity, GlanceCardConfig } from "../../cards/types";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { processEditorEntities } from "../process-editor-entities";
import {
EditorTarget,
@@ -58,8 +57,6 @@ export class HuiGlanceCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: GlanceCardConfig;
@internalProperty() private _configEntities?: ConfigEntity[];
@@ -103,94 +100,103 @@ export class HuiGlanceCardEditor extends LitElement
return html``;
}
const dir = computeRTLDirection(this.hass!);
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<hui-entity-editor
.hass=${this.hass}
.entities=${this._configEntities}
@entities-changed=${this._valueChanged}
></hui-entity-editor>
<div slot="advanced" class="card-config">
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.glance.columns"
)}
type="number"
.value=${this._columns}
.configValue=${"columns"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_name"
)}
</span>
<ha-switch
.checked=${this._config!.show_name !== false}
.configValue=${"show_name"}
@change=${this._valueChanged}
></ha-switch>
</ha-settings-row>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_icon"
)}
</span>
<ha-switch
.checked=${this._config!.show_icon !== false}
.configValue=${"show_icon"}
@change=${this._valueChanged}
>
</ha-switch>
</ha-settings-row>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_state"
)}
</span>
<ha-switch
.checked=${this._config!.show_state !== false}
.configValue=${"show_state"}
@change=${this._valueChanged}
>
</ha-switch>
</ha-settings-row>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.state_color"
)}
</span>
<ha-switch
.checked=${this._config!.state_color}
.configValue=${"state_color"}
@change=${this._valueChanged}
></ha-switch>
</ha-settings-row>
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="side-by-side">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.glance.columns"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
type="number"
.value=${this._columns}
.configValue=${"columns"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
</hui-config-element-template>
<div class="side-by-side">
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_name"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._config!.show_name !== false}
.configValue=${"show_name"}
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
</div>
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_icon"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._config!.show_icon !== false}
.configValue=${"show_icon"}
@change=${this._valueChanged}
>
</ha-switch>
</ha-formfield>
</div>
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_state"
)}
.dir=${dir}
>
<ha-switch
.checked=${this._config!.show_state !== false}
.configValue=${"show_state"}
@change=${this._valueChanged}
>
</ha-switch>
</ha-formfield>
</div>
</div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.state_color"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${this._config!.state_color}
.configValue=${"state_color"}
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
</div>
<hui-entity-editor
.hass=${this.hass}
.entities=${this._configEntities}
@entities-changed=${this._valueChanged}
></hui-entity-editor>
`;
}

View File

@@ -24,7 +24,6 @@ import { EntityId } from "../../common/structs/is-entity-id";
import "../../components/hui-entity-editor";
import { EntityConfig } from "../../entity-rows/types";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { processEditorEntities } from "../process-editor-entities";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -50,8 +49,6 @@ export class HuiHistoryGraphCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: HistoryGraphCardConfig;
@internalProperty() private _configEntities?: EntityConfig[];
@@ -84,48 +81,47 @@ export class HuiHistoryGraphCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="side-by-side">
<paper-input
type="number"
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hours_to_show"
)}
.value=${this._hours_to_show}
.configValue=${"hours_to_show"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<hui-entity-editor
.hass=${this.hass}
.entities=${this._configEntities}
@entities-changed=${this._valueChanged}
></hui-entity-editor>
</div>
<div slot="advanced" class="card-config">
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._title}"
.configValue="${"title"}"
@value-changed="${this._valueChanged}"
></paper-input>
<div class="side-by-side">
<paper-input
type="number"
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hours_to_show"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._hours_to_show}"
.configValue=${"hours_to_show"}
@value-changed="${this._valueChanged}"
></paper-input>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.refresh_interval"
)}
.value=${this._refresh_interval}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._refresh_interval}"
.configValue=${"refresh_interval"}
@value-changed=${this._valueChanged}
@value-changed="${this._valueChanged}"
></paper-input>
</div>
</hui-config-element-template>
<hui-entity-editor
.hass=${this.hass}
.entities="${this._configEntities}"
@entities-changed="${this._valueChanged}"
></hui-entity-editor>
</div>
`;
}

View File

@@ -15,7 +15,6 @@ import { HomeAssistant } from "../../../../types";
import { HumidifierCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -33,8 +32,6 @@ export class HuiHumidifierCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: HumidifierCardConfig;
public setConfig(config: HumidifierCardConfig): void {
@@ -60,42 +57,37 @@ export class HuiHumidifierCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<div slot="advanced" class="card-config">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value="${this._entity}"
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._name}"
.configValue="${"name"}"
@value-changed="${this._valueChanged}"
></paper-input>
<hui-theme-select-editor
.hass=${this.hass}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
`;
}

View File

@@ -13,7 +13,6 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types";
import { IframeCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -29,8 +28,6 @@ export class HuiIframeCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: IframeCardConfig;
public setConfig(config: IframeCardConfig): void {
@@ -56,41 +53,40 @@ export class HuiIframeCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.url"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value="${this._url}"
.configValue="${"url"}"
@value-changed="${this._valueChanged}"
></paper-input>
<div class="side-by-side">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.url"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value=${this._url}
.configValue=${"url"}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._title}"
.configValue="${"title"}"
@value-changed="${this._valueChanged}"
></paper-input>
</div>
<div slot="advanced" class="card-config">
<paper-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.aspect_ratio"
)}
.value=${this._aspect_ratio}
.configValue=${"aspect_ratio"}
@value-changed=${this._valueChanged}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._aspect_ratio}"
.configValue="${"aspect_ratio"}"
@value-changed="${this._valueChanged}"
></paper-input>
</div>
</hui-config-element-template>
</div>
`;
}

View File

@@ -19,7 +19,6 @@ import "../../components/hui-action-editor";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { actionConfigStruct, EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -40,8 +39,6 @@ export class HuiLightCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: LightCardConfig;
public setConfig(config: LightCardConfig): void {
@@ -88,73 +85,78 @@ export class HuiLightCardEditor extends LitElement
];
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@value-changed=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@value-changed=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<div class="side-by-side">
<paper-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<div slot="advanced" class="card-config">
<ha-icon-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.icon"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._icon}
.placeholder=${this._icon ||
stateIcon(this.hass.states[this._entity])}
.configValue=${"icon"}
@value-changed=${this._valueChanged}
></ha-icon-input>
<hui-action-editor
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hold_action"
)}
.hass=${this.hass}
.config=${this._hold_action}
.actions=${actions}
.configValue=${"hold_action"}
@value-changed=${this._valueChanged}
></hui-action-editor>
<hui-action-editor
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.double_tap_action"
)}
.hass=${this.hass}
.config=${this._double_tap_action}
.actions=${actions}
.configValue=${"double_tap_action"}
@value-changed=${this._valueChanged}
></hui-action-editor>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
<hui-action-editor
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hold_action"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config=${this._hold_action}
.actions=${actions}
.configValue=${"hold_action"}
@value-changed=${this._valueChanged}
></hui-action-editor>
<hui-action-editor
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.double_tap_action"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config=${this._double_tap_action}
.actions=${actions}
.configValue=${"double_tap_action"}
@value-changed=${this._valueChanged}
></hui-action-editor>
</div>
`;
}

View File

@@ -17,7 +17,6 @@ import { LogbookCardConfig } from "../../cards/types";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -34,8 +33,6 @@ export class HuiLogbookCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: LogbookCardConfig;
@internalProperty() private _configEntities?: string[];
@@ -68,51 +65,50 @@ export class HuiLogbookCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
type="number"
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hours_to_show"
)}
.value=${this._hours_to_show}
.configValue=${"hours_to_show"}
@value-changed=${this._valueChanged}
></paper-input>
<h3>
${`${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.entities"
)} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.required"
)})`}
</h3>
<ha-entities-picker
.hass=${this.hass}
.value=${this._configEntities}
@value-changed=${this._valueChanged}
>
</ha-entities-picker>
</div>
<div slot="advanced" class="card-config">
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="side-by-side">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
<paper-input
type="number"
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hours_to_show"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._hours_to_show}
.configValue=${"hours_to_show"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
</hui-config-element-template>
<h3>
${`${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.entities"
)} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.required"
)})`}
</h3>
<ha-entities-picker
.hass=${this.hass}
.value=${this._configEntities}
@value-changed=${this._valueChanged}
>
</ha-entities-picker>
</div>
`;
}

View File

@@ -19,8 +19,8 @@ import {
string,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-formfield";
import "../../../../components/ha-settings-row";
import "../../../../components/ha-switch";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { HomeAssistant } from "../../../../types";
@@ -29,7 +29,6 @@ import "../../components/hui-entity-editor";
import "../../components/hui-input-list-editor";
import { EntityConfig } from "../../entity-rows/types";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { processEditorEntities } from "../process-editor-entities";
import {
EditorTarget,
@@ -53,8 +52,6 @@ const cardConfigStruct = object({
export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: MapCardConfig;
@internalProperty() private _configEntities?: EntityConfig[];
@@ -97,84 +94,87 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._title}"
.configValue="${"title"}"
@value-changed="${this._valueChanged}"
></paper-input>
<div class="side-by-side">
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="side-by-side">
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.map.default_zoom"
)}
type="number"
.value=${this._default_zoom}
.configValue=${"default_zoom"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.map.hours_to_show"
)}
type="number"
.value=${this._hours_to_show}
.configValue=${"hours_to_show"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.map.dark_mode"
)}
</span>
<ha-switch
.checked=${this._dark_mode}
.configValue=${"dark_mode"}
@change=${this._valueChanged}
></ha-switch>
</ha-settings-row>
<hui-entity-editor
.hass=${this.hass}
.entities=${this._configEntities}
@entities-changed=${this._entitiesValueChanged}
></hui-entity-editor>
</div>
<div slot="advanced" class="card-config">
<paper-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.aspect_ratio"
)}
.value=${this._aspect_ratio}
.configValue=${"aspect_ratio"}
@value-changed=${this._valueChanged}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._aspect_ratio}"
.configValue="${"aspect_ratio"}"
@value-changed="${this._valueChanged}"
></paper-input>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.map.default_zoom"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
type="number"
.value="${this._default_zoom}"
.configValue="${"default_zoom"}"
@value-changed="${this._valueChanged}"
></paper-input>
<h3>
${this.hass.localize(
"ui.panel.lovelace.editor.card.map.geo_location_sources"
)}
</h3>
<div class="geo_location_sources">
<hui-input-list-editor
inputLabel=${this.hass.localize(
"ui.panel.lovelace.editor.card.map.source"
)}
.hass=${this.hass}
.value=${this._geo_location_sources}
.configValue=${"geo_location_sources"}
@value-changed=${this._valueChanged}
></hui-input-list-editor>
</div>
</div>
</hui-config-element-template>
<div class="side-by-side">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.map.dark_mode"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked="${this._dark_mode}"
.configValue="${"dark_mode"}"
@change="${this._valueChanged}"
></ha-switch
></ha-formfield>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.map.hours_to_show"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
type="number"
.value="${this._hours_to_show}"
.configValue="${"hours_to_show"}"
@value-changed="${this._valueChanged}"
></paper-input>
</div>
<hui-entity-editor
.hass=${this.hass}
.entities="${this._configEntities}"
@entities-changed="${this._entitiesValueChanged}"
></hui-entity-editor>
<h3>
${this.hass.localize(
"ui.panel.lovelace.editor.card.map.geo_location_sources"
)}
</h3>
<div class="geo_location_sources">
<hui-input-list-editor
inputLabel=${this.hass.localize(
"ui.panel.lovelace.editor.card.map.source"
)}
.hass=${this.hass}
.value="${this._geo_location_sources}"
.configValue="${"geo_location_sources"}"
@value-changed="${this._valueChanged}"
></hui-input-list-editor>
</div>
</div>
`;
}

View File

@@ -15,7 +15,6 @@ import { HomeAssistant } from "../../../../types";
import { MarkdownCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -31,8 +30,6 @@ export class HuiMarkdownCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: MarkdownCardConfig;
public setConfig(config: MarkdownCardConfig): void {
@@ -58,43 +55,38 @@ export class HuiMarkdownCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<paper-textarea
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.markdown.content"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value=${this._content}
.configValue=${"content"}
@keydown=${this._ignoreKeydown}
@value-changed=${this._valueChanged}
autocapitalize="none"
autocomplete="off"
spellcheck="false"
></paper-textarea>
</div>
<div slot="advanced" class="card-config">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._title}"
.configValue="${"title"}"
@value-changed="${this._valueChanged}"
></paper-input>
<paper-textarea
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.markdown.content"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value="${this._content}"
.configValue="${"content"}"
@keydown=${this._ignoreKeydown}
@value-changed="${this._valueChanged}"
autocapitalize="none"
autocomplete="off"
spellcheck="false"
></paper-textarea>
<hui-theme-select-editor
.hass=${this.hass}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
`;
}

View File

@@ -1,18 +1,18 @@
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 "../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../types";
import { MediaControlCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { assert, object, string, optional } from "superstruct";
const cardConfigStruct = object({
type: string(),
@@ -45,17 +45,17 @@ export class HuiMediaControlCardEditor extends LitElement
return html`
<div class="card-config">
<ha-entity-picker
allow-custom-entity
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.value="${this._entity}"
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
</div>
`;

View File

@@ -16,7 +16,6 @@ import { PictureCardConfig } from "../../cards/types";
import "../../components/hui-action-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { actionConfigStruct, EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -33,8 +32,6 @@ export class HuiPictureCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: PictureCardConfig;
public setConfig(config: PictureCardConfig): void {
@@ -66,51 +63,50 @@ export class HuiPictureCardEditor extends LitElement
const actions = ["navigate", "url", "call-service", "none"];
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.image"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value=${this._image}
.configValue=${"image"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<div slot="advanced" class="card-config">
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.image"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.value="${this._image}"
.configValue="${"image"}"
@value-changed="${this._valueChanged}"
></paper-input>
<div class="side-by-side">
<hui-action-editor
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.tap_action"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config=${this._tap_action}
.actions=${actions}
.configValue=${"tap_action"}
@value-changed=${this._valueChanged}
.config="${this._tap_action}"
.actions="${actions}"
.configValue="${"tap_action"}"
@value-changed="${this._valueChanged}"
></hui-action-editor>
<hui-action-editor
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hold_action"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config=${this._hold_action}
.actions=${actions}
.configValue=${"hold_action"}
@value-changed=${this._valueChanged}
.config="${this._hold_action}"
.actions="${actions}"
.configValue="${"hold_action"}"
@value-changed="${this._valueChanged}"
></hui-action-editor>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
</hui-config-element-template>
</div>
`;
}

View File

@@ -3,8 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResultArray,
CSSResult,
customElement,
html,
internalProperty,
@@ -14,8 +13,8 @@ import {
} from "lit-element";
import { assert, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-formfield";
import "../../../../components/ha-settings-row";
import "../../../../components/ha-switch";
import { ActionConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types";
@@ -24,7 +23,6 @@ import "../../components/hui-action-editor";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { actionConfigStruct, EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -50,8 +48,6 @@ export class HuiPictureEntityCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: PictureEntityCardConfig;
public setConfig(config: PictureEntityCardConfig): void {
@@ -110,137 +106,148 @@ export class HuiPictureEntityCardEditor extends LitElement
const actions = ["more-info", "toggle", "navigate", "call-service", "none"];
const views = ["auto", "live"];
const dir = computeRTLDirection(this.hass!);
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
allow-custom-entity
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value="${this._entity}"
.configValue=${"entity"}
@value-changed="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._name}"
.configValue="${"name"}"
@value-changed="${this._valueChanged}"
></paper-input>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.image"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._image}"
.configValue="${"image"}"
@value-changed="${this._valueChanged}"
></paper-input>
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.camera_image"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.value="${this._camera_image}"
.configValue=${"camera_image"}
@value-changed="${this._valueChanged}"
.includeDomains=${includeDomains}
allow-custom-entity
></ha-entity-picker>
<div class="side-by-side">
<paper-dropdown-menu
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
"ui.panel.lovelace.editor.card.generic.camera_view"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
@value-changed=${this._valueChanged}
></ha-entity-picker>
.configValue="${"camera_view"}"
@value-changed="${this._valueChanged}"
>
<paper-listbox
slot="dropdown-content"
.selected="${views.indexOf(this._camera_view)}"
>
${views.map((view) => {
return html` <paper-item>${view}</paper-item> `;
})}
</paper-listbox>
</paper-dropdown-menu>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.aspect_ratio"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._aspect_ratio}"
.configValue="${"aspect_ratio"}"
@value-changed="${this._valueChanged}"
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.image"
)}
.value=${this._image}
.configValue=${"image"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
</div>
<div class="side-by-side">
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_name"
)}
</span>
<ha-switch
.checked=${this._config!.show_name !== false}
.configValue=${"show_name"}
@change=${this._change}
></ha-switch>
</ha-settings-row>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
.dir=${dir}
>
<ha-switch
.checked="${this._config!.show_name !== false}"
.configValue="${"show_name"}"
@change="${this._change}"
></ha-switch
></ha-formfield>
</div>
<div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.show_state"
)}
</span>
<ha-switch
.checked=${this._config!.show_state !== false}
.configValue=${"show_state"}
@change=${this._change}
></ha-switch>
</ha-settings-row>
<hui-action-editor
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.tap_action"
)}
.hass=${this.hass}
.config=${this._tap_action}
.actions=${actions}
.configValue=${"tap_action"}
@value-changed=${this._valueChanged}
></hui-action-editor>
<ha-expansion-panel>
<span class="title" slot="title">
${this.hass.localize(
`ui.panel.lovelace.editor.card.picture-entity.camera_options`
)}
</span>
<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.camera_image"
)}
.hass=${this.hass}
.value=${this._camera_image}
.configValue=${"camera_image"}
@value-changed=${this._valueChanged}
.includeDomains=${includeDomains}
allow-custom-entity
></ha-entity-picker>
<paper-dropdown-menu
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.camera_view"
)}
.configValue=${"camera_view"}
@value-changed=${this._valueChanged}
.dir=${dir}
>
<paper-listbox
slot="dropdown-content"
.selected=${views.indexOf(this._camera_view)}
>
${views.map((view) => {
return html`<paper-item>${view}</paper-item>`;
})}
</paper-listbox>
</paper-dropdown-menu>
</ha-expansion-panel>
<ha-switch
.checked="${this._config!.show_state !== false}"
.configValue="${"show_state"}"
@change="${this._change}"
></ha-switch
></ha-formfield>
</div>
</div>
<div slot="advanced" class="card-config">
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.aspect_ratio"
)}
.value=${this._aspect_ratio}
.configValue=${"aspect_ratio"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="side-by-side">
<hui-action-editor
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hold_action"
)}
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.tap_action"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config=${this._hold_action}
.actions=${actions}
.configValue=${"hold_action"}
@value-changed=${this._valueChanged}
.config="${this._tap_action}"
.actions="${actions}"
.configValue="${"tap_action"}"
@value-changed="${this._valueChanged}"
></hui-action-editor>
<hui-action-editor
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hold_action"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config="${this._hold_action}"
.actions="${actions}"
.configValue="${"hold_action"}"
@value-changed="${this._valueChanged}"
></hui-action-editor>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
</hui-config-element-template>
</div>
`;
}
@@ -287,15 +294,8 @@ export class HuiPictureEntityCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResultArray {
return [
configElementStyle,
css`
paper-dropdown-menu {
width: 100%;
}
`,
];
static get styles(): CSSResult {
return configElementStyle;
}
}

View File

@@ -3,8 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResultArray,
CSSResult,
customElement,
html,
internalProperty,
@@ -23,7 +22,6 @@ import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { EntityConfig } from "../../entity-rows/types";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { processEditorEntities } from "../process-editor-entities";
import {
actionConfigStruct,
@@ -53,8 +51,6 @@ export class HuiPictureGlanceCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: PictureGlanceCardConfig;
@internalProperty() private _configEntities?: EntityConfig[];
@@ -116,114 +112,120 @@ export class HuiPictureGlanceCardEditor extends LitElement
const views = ["auto", "live"];
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<div class="card-config">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._title}"
.configValue="${"title"}"
@value-changed="${this._valueChanged}"
></paper-input>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.image"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._image}"
.configValue="${"image"}"
@value-changed="${this._valueChanged}"
></paper-input>
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.camera_image"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.value="${this._camera_image}"
.configValue=${"camera_image"}
@value-changed="${this._valueChanged}"
allow-custom-entity
.includeDomains=${includeDomains}
></ha-entity-picker>
<div class="side-by-side">
<paper-dropdown-menu
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.camera_view"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.configValue="${"camera_view"}"
@value-changed="${this._valueChanged}"
>
<paper-listbox
slot="dropdown-content"
.selected="${views.indexOf(this._camera_view)}"
>
${views.map((view) => {
return html` <paper-item>${view}</paper-item> `;
})}
</paper-listbox>
</paper-dropdown-menu>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.image"
)}
.value=${this._image}
.configValue=${"image"}
@value-changed=${this._valueChanged}
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.aspect_ratio"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._aspect_ratio}"
.configValue="${"aspect_ratio"}"
@value-changed="${this._valueChanged}"
></paper-input>
</div>
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.picture-glance.state_entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.value="${this._entity}"
.configValue=${"entity"}
@value-changed="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<div class="side-by-side">
<hui-action-editor
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.tap_action"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config=${this._tap_action}
.actions=${actions}
.configValue=${"tap_action"}
@value-changed=${this._valueChanged}
.config="${this._tap_action}"
.actions="${actions}"
.configValue="${"tap_action"}"
@value-changed="${this._valueChanged}"
></hui-action-editor>
<hui-action-editor
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hold_action"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.hass=${this.hass}
.config="${this._hold_action}"
.actions="${actions}"
.configValue="${"hold_action"}"
@value-changed="${this._valueChanged}"
></hui-action-editor>
</div>
<hui-entity-editor
.hass=${this.hass}
.entities=${this._configEntities}
@entities-changed=${this._valueChanged}
.entities="${this._configEntities}"
@entities-changed="${this._valueChanged}"
></hui-entity-editor>
<ha-expansion-panel>
<span class="title" slot="title">
${this.hass.localize(
`ui.panel.lovelace.editor.card.picture-entity.camera_options`
)}
</span>
<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.camera_image"
)}
.hass=${this.hass}
.value=${this._camera_image}
.configValue=${"camera_image"}
@value-changed=${this._valueChanged}
.includeDomains=${includeDomains}
allow-custom-entity
></ha-entity-picker>
<paper-dropdown-menu
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.camera_view"
)}
.configValue=${"camera_view"}
@value-changed=${this._valueChanged}
>
<paper-listbox
slot="dropdown-content"
.selected=${views.indexOf(this._camera_view)}
>
${views.map((view) => {
return html`<paper-item>${view}</paper-item>`;
})}
</paper-listbox>
</paper-dropdown-menu>
</ha-expansion-panel>
<div slot="advanced" class="card-config">
<ha-entity-picker
allow-custom-entity
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)}
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
@value-changed=${this._valueChanged}
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.aspect_ratio"
)}
.value=${this._aspect_ratio}
.configValue=${"aspect_ratio"}
@value-changed=${this._valueChanged}
></paper-input>
<hui-action-editor
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hold_action"
)}
.hass=${this.hass}
.config=${this._hold_action}
.actions=${actions}
.configValue=${"hold_action"}
@value-changed=${this._valueChanged}
></hui-action-editor>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<hui-theme-select-editor
.hass=${this.hass}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
`;
}
@@ -256,15 +258,8 @@ export class HuiPictureGlanceCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResultArray {
return [
configElementStyle,
css`
paper-dropdown-menu {
width: 100%;
}
`,
];
static get styles(): CSSResult {
return configElementStyle;
}
}

View File

@@ -16,7 +16,6 @@ import { HomeAssistant } from "../../../../types";
import { PlantStatusCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -34,8 +33,6 @@ export class HuiPlantStatusCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: PlantStatusCardConfig;
public setConfig(config: PlantStatusCardConfig): void {
@@ -61,42 +58,37 @@ export class HuiPlantStatusCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
allow-custom-entity
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<div slot="advanced" class="card-config">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value="${this._entity}"
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._name}"
.configValue="${"name"}"
@value-changed="${this._valueChanged}"
></paper-input>
<hui-theme-select-editor
.hass=${this.hass}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
`;
}

View File

@@ -17,13 +17,11 @@ import { stateIcon } from "../../../../common/entity/state_icon";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon-input";
import "../../../../components/ha-settings-row";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types";
import { SensorCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -46,8 +44,6 @@ export class HuiSensorCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: SensorCardConfig;
public setConfig(config: SensorCardConfig): void {
@@ -95,99 +91,105 @@ export class HuiSensorCardEditor extends LitElement
const graphs = ["line", "none"];
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="side-by-side">
<paper-dropdown-menu
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.sensor.graph_type"
)}
.configValue=${"graph"}
@value-changed=${this._valueChanged}
>
<paper-listbox
slot="dropdown-content"
.selected=${graphs.indexOf(this._graph)}
>
${graphs.map((graph) => {
return html`<paper-item>${graph}</paper-item>`;
})}
</paper-listbox>
</paper-dropdown-menu>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hours_to_show"
)}
type="number"
.value=${this._hours_to_show}
.configValue=${"hours_to_show"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
</div>
<div slot="advanced" class="card-config">
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.sensor.show_more_detail"
)}
</span>
<ha-switch
.checked=${this._detail === 2}
.configValue=${"detail"}
@change=${this._change}
></ha-switch>
</ha-settings-row>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.unit"
)}
.value=${this._unit}
.configValue=${"unit"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
<div class="side-by-side">
<ha-icon-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.icon"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._icon}
.placeholder=${this._icon ||
stateIcon(this.hass.states[this._entity])}
.configValue=${"icon"}
@value-changed=${this._valueChanged}
></ha-icon-input>
<paper-dropdown-menu
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.sensor.graph_type"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.configValue=${"graph"}
@value-changed=${this._valueChanged}
>
<paper-listbox
slot="dropdown-content"
.selected=${graphs.indexOf(this._graph)}
>
${graphs.map((graph) => {
return html`<paper-item>${graph}</paper-item>`;
})}
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="side-by-side">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.unit"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._unit}
.configValue=${"unit"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-formfield
label=${this.hass.localize(
"ui.panel.lovelace.editor.card.sensor.show_more_detail"
)}
>
<ha-switch
.checked=${this._detail === 2}
.configValue=${"detail"}
@change=${this._change}
></ha-switch>
</ha-formfield>
</div>
<div class="side-by-side">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hours_to_show"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
type="number"
.value=${this._hours_to_show}
.configValue=${"hours_to_show"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
</hui-config-element-template>
</div>
`;
}

View File

@@ -4,20 +4,19 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types";
import { ShoppingListCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { string, assert, object, optional } from "superstruct";
const cardConfigStruct = object({
type: string(),
@@ -30,8 +29,6 @@ export class HuiShoppingListEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: ShoppingListCardConfig;
public setConfig(config: ShoppingListCardConfig): void {
@@ -53,38 +50,33 @@ export class HuiShoppingListEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
${!isComponentLoaded(this.hass, "shopping_list")
? html`
<div class="error">
${this.hass.localize(
"ui.panel.lovelace.editor.card.shopping-list.integration_not_loaded"
)}
</div>
`
: ""}
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<div slot="advanced" class="card-config">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<div class="card-config">
${!isComponentLoaded(this.hass, "shopping_list")
? html`
<div class="error">
${this.hass.localize(
"ui.panel.lovelace.editor.card.shopping-list.integration_not_loaded"
)}
</div>
`
: ""}
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.title"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._title}"
.configValue="${"title"}"
@value-changed="${this._valueChanged}"
></paper-input>
<hui-theme-select-editor
.hass=${this.hass}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
`;
}

View File

@@ -15,7 +15,6 @@ import { HomeAssistant } from "../../../../types";
import { ThermostatCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -33,8 +32,6 @@ export class HuiThermostatCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: ThermostatCardConfig;
public setConfig(config: ThermostatCardConfig): void {
@@ -60,42 +57,37 @@ export class HuiThermostatCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
</div>
<div slot="advanced" class="card-config">
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value="${this._entity}"
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value="${this._name}"
.configValue="${"name"}"
@value-changed="${this._valueChanged}"
></paper-input>
<hui-theme-select-editor
.hass=${this.hass}
.value="${this._theme}"
.configValue="${"theme"}"
@value-changed="${this._valueChanged}"
></hui-theme-select-editor>
</div>
`;
}

View File

@@ -9,16 +9,15 @@ import {
} from "lit-element";
import { assert, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-attribute-picker";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-formfield";
import "../../../../components/ha-settings-row";
import "../../../../components/ha-switch";
import "../../../../components/entity/ha-entity-attribute-picker";
import { HomeAssistant } from "../../../../types";
import { WeatherForecastCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import "../hui-config-element-template";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
@@ -38,8 +37,6 @@ export class HuiWeatherForecastCardEditor extends LitElement
implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public isAdvanced?: boolean;
@internalProperty() private _config?: WeatherForecastCardConfig;
public setConfig(config: WeatherForecastCardConfig): void {
@@ -73,56 +70,31 @@ export class HuiWeatherForecastCardEditor extends LitElement
}
return html`
<hui-config-element-template
.hass=${this.hass}
.isAdvanced=${this.isAdvanced}
>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.entity"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.required"
)})"
.hass=${this.hass}
.value=${this._entity}
.configValue=${"entity"}
.includeDomains=${includeDomains}
@change=${this._valueChanged}
allow-custom-entity
></ha-entity-picker>
<div class="side-by-side">
<paper-input
.label=${this.hass.localize(
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-settings-row three-line>
<span slot="heading">
${this.hass.localize(
"ui.panel.lovelace.editor.card.weather-forecast.show_forecast"
)}
</span>
<ha-switch
.checked=${this._config!.show_forecast !== false}
.configValue=${"show_forecast"}
@change=${this._valueChanged}
></ha-switch>
</ha-settings-row>
</div>
<div slot="advanced" class="card-config">
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${this._entity}
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.secondary_info_attribute"
)}
.value=${this._secondary_info_attribute}
.configValue=${"secondary_info_attribute"}
@value-changed=${this._valueChanged}
></ha-entity-attribute-picker>
<hui-theme-select-editor
.hass=${this.hass}
.value=${this._theme}
@@ -130,7 +102,33 @@ export class HuiWeatherForecastCardEditor extends LitElement
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
</hui-config-element-template>
<div class="side-by-side">
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${this._entity}
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.secondary_info_attribute"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._secondary_info_attribute}
.configValue=${"secondary_info_attribute"}
@value-changed=${this._valueChanged}
></ha-entity-attribute-picker>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.weather-forecast.show_forecast"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${this._config!.show_forecast !== false}
.configValue=${"show_forecast"}
@change=${this._valueChanged}
></ha-switch
></ha-formfield>
</div>
</div>
`;
}

View File

@@ -53,7 +53,7 @@ export class HuiCreateDialogHeaderFooter extends LitElement
`ui.panel.lovelace.editor.header-footer.choose_header_footer`,
"type",
this.hass!.localize(
`ui.panel.lovelace.editor.header-footer.${this._params.type}.name`
`ui.panel.lovelace.editor.header-footer.${this._params.type}`
)
)
)}

View File

@@ -1,5 +1,5 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiChevronRight } from "@mdi/js";
import { mdiClose, mdiPencil, mdiPlus } from "@mdi/js";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
@@ -31,26 +31,56 @@ export class HuiHeaderFooterEditor extends LitElement {
protected render(): TemplateResult {
return html`
<div @click=${this._click}>
<div>
<span>
${this.hass.localize(
`ui.panel.lovelace.editor.header-footer.${this.configValue}.manage`
)}
`ui.panel.lovelace.editor.header-footer.${this.configValue}`
)}:
${!this.config?.type
? this.hass!.localize("ui.panel.lovelace.editor.common.none")
: this.hass!.localize(
`ui.panel.lovelace.editor.header-footer.types.${this.config?.type}.name`
)}
</span>
<ha-svg-icon .path=${mdiChevronRight}></ha-svg-icon>
</div>
<div>
${!this.config?.type
? html`
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.panel.lovelace.editor.common.add"
)}
class="add-icon"
@click=${this._add}
>
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
</mwc-icon-button>
`
: html`
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.panel.lovelace.editor.common.clear"
)}
class="remove-icon"
@click=${this._delete}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.panel.lovelace.editor.common.edit"
)}
class="edit-icon"
@click=${this._edit}
>
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button>
`}
</div>
`;
}
private _click(): void {
if (!this.config?.type) {
showCreateHeaderFooterDialog(this, {
pickHeaderFooter: (config) => this._elementPicked(config),
type: this.configValue,
});
return;
}
private _edit(): void {
fireEvent(this, "edit-detail-element", {
subElementConfig: {
elementConfig: this.config,
@@ -59,6 +89,13 @@ export class HuiHeaderFooterEditor extends LitElement {
});
}
private _add(): void {
showCreateHeaderFooterDialog(this, {
pickHeaderFooter: (config) => this._elementPicked(config),
type: this.configValue,
});
}
private _elementPicked(config: LovelaceHeaderFooterConfig): void {
fireEvent(this, "value-changed", { value: config });
fireEvent(this, "edit-detail-element", {
@@ -69,20 +106,35 @@ export class HuiHeaderFooterEditor extends LitElement {
});
}
private _delete(): void {
fireEvent(this, "value-changed", { value: "" });
}
static get styles(): CSSResult {
return css`
:host {
font-size: 16px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
padding-bottom: 12px;
}
:host > div {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 12px;
min-height: 48px;
cursor: pointer;
}
ha-svg-icon {
mwc-icon-button,
.header-footer-icon {
--mdc-icon-button-size: 36px;
color: var(--secondary-text-color);
}
.header-footer-icon {
padding-right: 8px;
}
`;
}
}

View File

@@ -1,46 +0,0 @@
import {
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../components/ha-expansion-panel";
import { HomeAssistant } from "../../../types";
import { configElementStyle } from "./config-elements/config-elements-style";
@customElement("hui-config-element-template")
export class HuiConfigElementTemplate extends LitElement {
public hass!: HomeAssistant;
@property({ type: Boolean }) public isAdvanced? = false;
protected render(): TemplateResult {
return html`
<slot></slot>
${this.isAdvanced
? html`
<ha-expansion-panel>
<span class="title" slot="title">
${this.hass.localize(
`ui.panel.lovelace.editor.common.advanced_options`
)}
</span>
<slot name="advanced"></slot>
</ha-expansion-panel>
`
: ""}
`;
}
static get styles(): CSSResult {
return configElementStyle;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-config-element-template": HuiConfigElementTemplate;
}
}

View File

@@ -31,11 +31,7 @@ import { GUISupportError } from "./gui-support-error";
import { EditSubElementEvent, GUIModeChangedEvent } from "./types";
export interface ConfigChangedEvent {
config:
| LovelaceCardConfig
| LovelaceRowConfig
| LovelaceHeaderFooterConfig
| undefined;
config: LovelaceCardConfig | LovelaceRowConfig | LovelaceHeaderFooterConfig;
error?: string;
guiModeAvailable?: boolean;
}
@@ -59,8 +55,6 @@ export abstract class HuiElementEditor<T> extends LitElement {
@property({ attribute: false }) public lovelace?: LovelaceConfig;
@property({ attribute: false }) public isAdvanced?: boolean;
@internalProperty() private _yaml?: string;
@internalProperty() private _config?: T;
@@ -150,14 +144,10 @@ export abstract class HuiElementEditor<T> extends LitElement {
});
}
public toggleMode(): void {
public toggleMode() {
this.GUImode = !this.GUImode;
}
public remove(): void {
this._configElement?.remove();
}
public refreshYamlEditor(focus = false) {
if (this._configElement?.refreshYamlEditor) {
this._configElement.refreshYamlEditor(focus);
@@ -247,9 +237,6 @@ export abstract class HuiElementEditor<T> extends LitElement {
) {
this._configElement.lovelace = this.lovelace;
}
if (this._configElement && changedProperties.has("isAdvanced")) {
this._configElement.isAdvanced = this.isAdvanced;
}
}
private _handleUIConfigChanged(ev: UIConfigChangedEvent) {
@@ -296,7 +283,6 @@ export abstract class HuiElementEditor<T> extends LitElement {
}
configElement.hass = this.hass;
configElement.isAdvanced = this.isAdvanced;
if ("lovelace" in configElement) {
configElement.lovelace = this.lovelace;
}

View File

@@ -1,6 +1,5 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button";
import { mdiChevronRight, mdiDragVerticalVariant } from "@mdi/js";
import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js";
import {
css,
CSSResult,
@@ -20,7 +19,7 @@ import Sortable, {
} from "sortablejs/modular/sortable.core.esm";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker";
import "../../../components/ha-card";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
import "../../../components/ha-svg-icon";
import { sortableStyles } from "../../../resources/ha-sortable-style";
import { HomeAssistant } from "../../../types";
@@ -64,72 +63,78 @@ export class HuiEntitiesCardRowEditor extends LitElement {
}
return html`
<div class="title">
<span>
${this.label ||
`${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.entities"
)}:`}
</span>
<mwc-button
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.card.entities.add_row"
)}
@click=${this._addEntity}
></mwc-button>
</div>
<h3>
${this.label ||
`${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.entities"
)} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.required"
)})`}
</h3>
<div class="entities">
${guard([this.entities, this._renderEmptySortable], () =>
this._renderEmptySortable
? ""
: this.entities!.map((entityConf, index) => {
const stateObj = this.hass!.states[
(entityConf as EntityConfig).entity
];
return html`
<ha-card
outlined
class="entity"
.index=${index}
@click=${this._editRow}
>
<ha-svg-icon
class="handle"
.path=${mdiDragVerticalVariant}
></ha-svg-icon>
<div class="info">
<span class="primary">
${entityConf.type
? html`
${this.hass!.localize(
`ui.panel.lovelace.editor.card.entities.entity_row.${entityConf.type}`
)}
`
: html`
${(entityConf as EntityConfig).name ||
stateObj?.attributes.friendly_name ||
(entityConf as EntityConfig).entity}
`}
</span>
<span class="secondary">
${entityConf.type
? html`
${this.hass!.localize(
"ui.panel.lovelace.editor.card.entities.edit_special_row"
)}
`
: html`${(entityConf as EntityConfig).entity}`}
</span>
</div>
<ha-svg-icon
<div class="entity">
<ha-svg-icon class="handle" .path=${mdiDrag}></ha-svg-icon>
${entityConf.type
? html`
<div class="special-row">
<div>
<span>
${this.hass!.localize(
`ui.panel.lovelace.editor.card.entities.entity_row.${entityConf.type}`
)}
</span>
<span class="secondary"
>${this.hass!.localize(
"ui.panel.lovelace.editor.card.entities.edit_special_row"
)}</span
>
</div>
</div>
`
: html`
<ha-entity-picker
allow-custom-entity
hideClearIcon
.hass=${this.hass}
.value=${(entityConf as EntityConfig).entity}
.index=${index}
@value-changed=${this._valueChanged}
></ha-entity-picker>
`}
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.components.entity.entity-picker.clear"
)}
class="remove-icon"
.index=${index}
@click=${this._removeRow}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.components.entity.entity-picker.edit"
)}
class="edit-icon"
.path=${mdiChevronRight}
></ha-svg-icon>
</ha-card>
.index=${index}
@click=${this._editRow}
>
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button>
</div>
`;
})
)}
</div>
<ha-entity-picker
.hass=${this.hass}
@value-changed=${this._addEntity}
></ha-entity-picker>
`;
}
@@ -184,20 +189,16 @@ export class HuiEntitiesCardRowEditor extends LitElement {
});
}
private async _addEntity(): Promise<void> {
private async _addEntity(ev: CustomEvent): Promise<void> {
const value = ev.detail.value;
if (value === "") {
return;
}
const newConfigEntities = this.entities!.concat({
entity: Object.keys(this.hass!.states)[0],
entity: value as string,
});
(ev.target as HaEntityPicker).value = "";
fireEvent(this, "entities-changed", { entities: newConfigEntities });
const index = newConfigEntities.length - 1;
fireEvent(this, "edit-detail-element", {
subElementConfig: {
index,
type: "row",
elementConfig: newConfigEntities[index],
},
});
}
private _rowMoved(ev: SortableEvent): void {
@@ -212,6 +213,32 @@ export class HuiEntitiesCardRowEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newEntities });
}
private _removeRow(ev: CustomEvent): void {
const index = (ev.currentTarget as any).index;
const newConfigEntities = this.entities!.concat();
newConfigEntities.splice(index, 1);
fireEvent(this, "entities-changed", { entities: newConfigEntities });
}
private _valueChanged(ev: CustomEvent): void {
const value = ev.detail.value;
const index = (ev.target as any).index;
const newConfigEntities = this.entities!.concat();
if (value === "") {
newConfigEntities.splice(index, 1);
} else {
newConfigEntities[index] = {
...newConfigEntities[index],
entity: value!,
};
}
fireEvent(this, "entities-changed", { entities: newConfigEntities });
}
private _editRow(ev: CustomEvent): void {
const index = (ev.currentTarget as any).index;
fireEvent(this, "edit-detail-element", {
@@ -227,62 +254,43 @@ export class HuiEntitiesCardRowEditor extends LitElement {
return [
sortableStyles,
css`
.title {
font-size: 18px;
display: flex;
justify-content: space-between;
align-items: center;
}
.entity {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
margin: 8px 0;
cursor: pointer;
}
.handle {
min-width: 18px;
--mdc-icon-size: 18px;
.entity .handle {
padding-right: 8px;
cursor: move;
color: var(--secondary-text-color);
}
.entity ha-entity-picker {
flex-grow: 1;
}
.special-row {
height: 60px;
font-size: 16px;
display: flex;
align-items: center;
justify-content: space-between;
flex-grow: 1;
}
.info {
.special-row div {
display: flex;
flex-direction: column;
overflow: hidden;
text-overflow: ellipsis;
overflow: hidden;
flex-grow: 1;
}
.remove-icon,
.edit-icon {
min-width: 24px;
--mdc-icon-button-size: 36px;
color: var(--secondary-text-color);
}
.primary {
text-overflow: ellipsis;
overflow: hidden;
}
.secondary {
font-size: 12px;
color: var(--secondary-text-color);
text-overflow: ellipsis;
overflow: hidden;
}
`,
];

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