Pick unused entities for lovelace cards (#3614)

* Pick unused entities for lovelace cards

* Type

* Table layout for unused entities

* properties

* remove unused import

* mwc-button

Need to find a way to set the color

* add icons to pick view dialog

* Comments

* Lint

* Restore unused entities for yaml mode

* Remove _elements

* decorators, types, comments

* flexbox + comments

* remove unused import
This commit is contained in:
Bram Kragten 2019-09-08 22:43:28 +02:00 committed by Paulus Schoutsen
parent e19c210af2
commit 7e7158b816
22 changed files with 746 additions and 241 deletions

View File

@ -17,9 +17,11 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@material/mwc-base": "^0.6.0", "@material/mwc-base": "^0.8.0",
"@material/mwc-button": "^0.6.0", "@material/mwc-button": "^0.8.0",
"@material/mwc-ripple": "^0.6.0", "@material/mwc-checkbox": "^0.8.0",
"@material/mwc-fab": "^0.8.0",
"@material/mwc-ripple": "0.8.0",
"@mdi/svg": "4.3.95", "@mdi/svg": "4.3.95",
"@polymer/app-layout": "^3.0.2", "@polymer/app-layout": "^3.0.2",
"@polymer/app-localize-behavior": "^3.0.1", "@polymer/app-localize-behavior": "^3.0.1",
@ -84,7 +86,7 @@
"jquery": "^3.4.0", "jquery": "^3.4.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"leaflet": "^1.4.0", "leaflet": "^1.4.0",
"lit-element": "^2.2.0", "lit-element": "^2.2.1",
"lit-html": "^1.1.0", "lit-html": "^1.1.0",
"marked": "^0.6.1", "marked": "^0.6.1",
"mdn-polyfills": "^5.16.0", "mdn-polyfills": "^5.16.0",

View File

@ -0,0 +1,20 @@
import { Constructor, customElement } from "lit-element";
import "@material/mwc-checkbox";
// tslint:disable-next-line
import { Checkbox } from "@material/mwc-checkbox";
// tslint:disable-next-line
const MwcCheckbox = customElements.get("mwc-checkbox") as Constructor<Checkbox>;
@customElement("ha-checkbox")
export class HaCheckbox extends MwcCheckbox {
protected firstUpdated() {
super.firstUpdated();
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-checkbox": HaCheckbox;
}
}

49
src/components/ha-fab.ts Normal file
View File

@ -0,0 +1,49 @@
import {
classMap,
html,
customElement,
Constructor,
} from "@material/mwc-base/base-element";
import { ripple } from "@material/mwc-ripple/ripple-directive.js";
import "@material/mwc-fab";
// tslint:disable-next-line
import { Fab } from "@material/mwc-fab";
// tslint:disable-next-line
const MwcFab = customElements.get("mwc-fab") as Constructor<Fab>;
@customElement("ha-fab")
export class HaFab extends MwcFab {
// We override the render method because we don't have an icon font and mwc-fab doesn't support our svg-icon sets.
// Based on version mwc-fab 0.8
protected render() {
const classes = {
"mdc-fab--mini": this.mini,
"mdc-fab--exited": this.exited,
"mdc-fab--extended": this.extended,
};
const showLabel = this.label !== "" && this.extended;
return html`
<button
.ripple="${ripple()}"
class="mdc-fab ${classMap(classes)}"
?disabled="${this.disabled}"
aria-label="${this.label || this.icon}"
>
${showLabel && this.showIconAtEnd ? this.label : ""}
${this.icon
? html`
<ha-icon .icon=${this.icon}></ha-icon>
`
: ""}
${showLabel && !this.showIconAtEnd ? this.label : ""}
</button>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-fab": HaFab;
}
}

View File

@ -43,7 +43,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
} }
public static getStubConfig() { public static getStubConfig() {
return { states: ["arm_home", "arm_away"] }; return { states: ["arm_home", "arm_away"], entity: "" };
} }
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;

View File

@ -37,7 +37,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return document.createElement("hui-gauge-card-editor"); return document.createElement("hui-gauge-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(): object {
return {}; return { entity: "" };
} }
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;

View File

@ -8,6 +8,10 @@ import "../../../data/ha-state-history-data";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
class HuiHistoryGraphCard extends PolymerElement { class HuiHistoryGraphCard extends PolymerElement {
static getStubConfig() {
return { entities: [] };
}
static get template() { static get template() {
return html` return html`
<style> <style>

View File

@ -33,7 +33,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
return document.createElement("hui-light-card-editor"); return document.createElement("hui-light-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(): object {
return {}; return { entity: "" };
} }
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;

View File

@ -9,7 +9,7 @@ class HuiMediaControlCard extends LegacyWrapperCard {
} }
static getStubConfig() { static getStubConfig() {
return {}; return { entity: "" };
} }
constructor() { constructor() {

View File

@ -146,7 +146,7 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
} }
public static getStubConfig(): object { public static getStubConfig(): object {
return {}; return { entity: "" };
} }
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;

View File

@ -36,6 +36,7 @@ const SPECIAL_TYPES = new Set([
"section", "section",
"weblink", "weblink",
"cast", "cast",
"select",
]); ]);
const DOMAIN_TO_ELEMENT_TYPE = { const DOMAIN_TO_ELEMENT_TYPE = {
alert: "toggle", alert: "toggle",

View File

@ -1,8 +1,5 @@
import { directive, PropertyPart } from "lit-html"; import { directive, PropertyPart } from "lit-html";
// See https://github.com/home-assistant/home-assistant-polymer/pull/2457 import "@material/mwc-ripple";
// on how to undo mwc -> paper migration
// import "@material/mwc-ripple";
import "@polymer/paper-ripple";
const isTouch = const isTouch =
"ontouchstart" in window || "ontouchstart" in window ||
@ -19,7 +16,7 @@ interface LongPressElement extends Element {
class LongPress extends HTMLElement implements LongPress { class LongPress extends HTMLElement implements LongPress {
public holdTime: number; public holdTime: number;
protected ripple: any; public ripple: any;
protected timer: number | undefined; protected timer: number | undefined;
protected held: boolean; protected held: boolean;
protected cooldownStart: boolean; protected cooldownStart: boolean;
@ -28,7 +25,7 @@ class LongPress extends HTMLElement implements LongPress {
constructor() { constructor() {
super(); super();
this.holdTime = 500; this.holdTime = 500;
this.ripple = document.createElement("paper-ripple"); this.ripple = document.createElement("mwc-ripple");
this.timer = undefined; this.timer = undefined;
this.held = false; this.held = false;
this.cooldownStart = false; this.cooldownStart = false;
@ -37,7 +34,6 @@ class LongPress extends HTMLElement implements LongPress {
public connectedCallback() { public connectedCallback() {
Object.assign(this.style, { Object.assign(this.style, {
borderRadius: "50%", // paper-ripple
position: "absolute", position: "absolute",
width: isTouch ? "100px" : "50px", width: isTouch ? "100px" : "50px",
height: isTouch ? "100px" : "50px", height: isTouch ? "100px" : "50px",
@ -46,9 +42,7 @@ class LongPress extends HTMLElement implements LongPress {
}); });
this.appendChild(this.ripple); this.appendChild(this.ripple);
this.ripple.style.color = "#03a9f4"; // paper-ripple this.ripple.primary = true;
this.ripple.style.color = "var(--primary-color)"; // paper-ripple
// this.ripple.primary = true;
[ [
"touchcancel", "touchcancel",
@ -154,17 +148,14 @@ class LongPress extends HTMLElement implements LongPress {
top: `${y}px`, top: `${y}px`,
display: null, display: null,
}); });
this.ripple.holdDown = true; // paper-ripple this.ripple.disabled = false;
this.ripple.simulatedRipple(); // paper-ripple this.ripple.active = true;
// this.ripple.disabled = false; this.ripple.unbounded = true;
// this.ripple.active = true;
// this.ripple.unbounded = true;
} }
private stopAnimation() { private stopAnimation() {
this.ripple.holdDown = false; // paper-ripple this.ripple.active = false;
// this.ripple.active = false; this.ripple.disabled = true;
// this.ripple.disabled = true;
this.style.display = "none"; this.style.display = "none";
} }
} }

View File

@ -72,7 +72,7 @@ export class HuiDialogEditCard extends LitElement {
? html` ? html`
<hui-card-picker <hui-card-picker
.hass="${this.hass}" .hass="${this.hass}"
@config-changed="${this._handleConfigChanged}" @config-changed="${this._handleCardPicked}"
></hui-card-picker> ></hui-card-picker>
` `
: html` : html`
@ -220,6 +220,18 @@ export class HuiDialogEditCard extends LitElement {
]; ];
} }
private _handleCardPicked(ev) {
this._cardConfig = ev.detail.config;
if (this._params!.entities && this._params!.entities.length > 0) {
if (Object.keys(this._cardConfig!).includes("entities")) {
this._cardConfig!.entities = this._params!.entities;
} else if (Object.keys(this._cardConfig!).includes("entity")) {
this._cardConfig!.entity = this._params!.entities[0];
}
}
this._error = ev.detail.error;
}
private _handleConfigChanged(ev) { private _handleConfigChanged(ev) {
this._cardConfig = ev.detail.config; this._cardConfig = ev.detail.config;
this._error = ev.detail.error; this._error = ev.detail.error;

View File

@ -15,6 +15,7 @@ const dialogTag = "hui-dialog-edit-card";
export interface EditCardDialogParams { export interface EditCardDialogParams {
lovelace: Lovelace; lovelace: Lovelace;
path: [number] | [number, number]; path: [number] | [number, number];
entities?: string[]; // We can pass entity id's that will be added to the config when a card is picked
} }
const registerEditCardDialog = (element: HTMLElement): Event => const registerEditCardDialog = (element: HTMLElement): Event =>

View File

@ -0,0 +1,79 @@
import {
html,
LitElement,
TemplateResult,
customElement,
property,
} from "lit-element";
import "../../../../components/dialog/ha-paper-dialog";
import { toggleAttribute } from "../../../../common/dom/toggle_attribute";
import "../../components/hui-views-list";
// tslint:disable-next-line:no-duplicate-imports
import { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
import { SelectViewDialogParams } from "./show-select-view-dialog";
import { PolymerChangedEvent } from "../../../../polymer-types";
@customElement("hui-dialog-select-view")
export class HuiDialogSelectView extends LitElement {
@property() private _params?: SelectViewDialogParams;
public async showDialog(params: SelectViewDialogParams): Promise<void> {
this._params = params;
await this.updateComplete;
}
protected updated(changedProps) {
super.updated(changedProps);
toggleAttribute(
this,
"hide-icons",
this._params!.lovelace!.config
? !this._params!.lovelace!.config.views.some((view) => view.icon)
: true
);
}
protected render(): TemplateResult | void {
if (!this._params) {
return html``;
}
return html`
<ha-paper-dialog
with-backdrop
opened
@opened-changed="${this._openedChanged}"
>
<h2>Choose a view</h2>
<hui-views-list
.lovelaceConfig=${this._params!.lovelace.config}
@view-selected=${this._selectView}>
</hui-view-list>
</ha-paper-dialog>
`;
}
private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}
private _selectView(e: CustomEvent): void {
const view: number = e.detail.view;
this._params!.viewSelectedCallback(view);
this._dialog.close();
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!(ev.detail as any).value) {
this._params = undefined;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-dialog-select-view": HuiDialogSelectView;
}
}

View File

@ -0,0 +1,19 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import { Lovelace } from "../../types";
export interface SelectViewDialogParams {
lovelace: Lovelace;
viewSelectedCallback: (view: number) => void;
}
export const showSelectViewDialog = (
element: HTMLElement,
selectViewDialogParams: SelectViewDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "hui-dialog-select-view",
dialogImport: () =>
import(/* webpackChunkName: "hui-dialog-select-view" */ "./hui-dialog-select-view"),
dialogParams: selectViewDialogParams,
});
};

View File

@ -0,0 +1,151 @@
import {
html,
LitElement,
TemplateResult,
customElement,
property,
css,
CSSResult,
} from "lit-element";
import { Checkbox } from "@material/mwc-checkbox";
import { HomeAssistant } from "../../../../types";
import computeStateName from "../../../../common/entity/compute_state_name";
import computeDomain from "../../../../common/entity/compute_domain";
import "../../../../components/ha-checkbox";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-relative-time";
import "../../../../components/ha-icon";
import { fireEvent } from "../../../../common/dom/fire_event";
declare global {
// for fire event
interface HASSDomEvents {
"entity-selection-changed": EntitySelectionChangedEvent;
}
}
export interface EntitySelectionChangedEvent {
entity: string;
selected: boolean;
}
@customElement("hui-select-row")
class HuiSelectRow extends LitElement {
@property() public hass!: HomeAssistant;
@property() public entity?: string;
@property() public selectable = true;
protected render(): TemplateResult | void {
if (!this.entity) {
return html``;
}
const stateObj = this.entity ? this.hass.states[this.entity] : undefined;
if (!stateObj) {
return html``;
}
return html`
<div class="flex-row" role="rowgroup">
<div class="flex-cell" role="cell">
${this.selectable
? html`
<ha-checkbox @change=${this._handleSelect}></ha-checkbox>
`
: ""}
<state-badge .hass=${this.hass} .stateObj=${stateObj}></state-badge>
${computeStateName(stateObj)}
</div>
<div class="flex-cell" role="cell">${stateObj.entity_id}</div>
<div class="flex-cell" role="cell">
${computeDomain(stateObj.entity_id)}
</div>
<div class="flex-cell" role="cell">
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.last_changed}
></ha-relative-time>
</div>
</div>
`;
}
private _handleSelect(ev: Event): void {
const checkbox = ev.currentTarget as Checkbox;
fireEvent(this, "entity-selection-changed", {
entity: this.entity!,
selected: checkbox.checked as boolean,
});
}
static get styles(): CSSResult {
return css`
div {
box-sizing: border-box;
}
.flex-row {
display: flex;
flex-flow: row wrap;
}
.flex-row:hover {
background: var(--table-row-alternative-background-color, #eee);
}
.flex-cell {
width: calc(100% / 4);
padding: 12px 24px;
border-bottom: 1px solid #e0e0e0;
overflow: hidden;
text-overflow: ellipsis;
line-height: 40px;
}
@media all and (max-width: 767px) {
.flex-cell {
width: calc(100% / 3);
padding-top: 0;
}
.flex-cell:first-child {
width: 100%;
padding-top: 12px;
padding-bottom: 0;
border-bottom: 0;
}
}
@media all and (max-width: 430px) {
.flex-cell {
border-bottom: 0;
padding: 0 24px;
}
.flex-cell:first-child {
padding-top: 12px;
}
.flex-cell:last-child {
padding-bottom: 12px;
border-bottom: 1px solid #e0e0e0;
}
.flex-cell {
width: 100%;
}
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-select-row": HuiSelectRow;
}
}

View File

@ -0,0 +1,213 @@
import {
html,
LitElement,
TemplateResult,
PropertyValues,
property,
customElement,
css,
CSSResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../../components/ha-fab";
import "./hui-select-row";
import { computeRTL } from "../../../../common/util/compute_rtl";
import { computeUnusedEntities } from "../../common/compute-unused-entities";
import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
import { showEditCardDialog } from "../card-editor/show-edit-card-dialog";
import { HomeAssistant } from "../../../../types";
import { Lovelace } from "../../types";
import { LovelaceConfig } from "../../../../data/lovelace";
@customElement("hui-unused-entities")
export class HuiUnusedEntities extends LitElement {
@property() public lovelace?: Lovelace;
@property() public hass?: HomeAssistant;
@property() private _unusedEntities: string[] = [];
private _selectedEntities: string[] = [];
private get _config(): LovelaceConfig {
return this.lovelace!.config;
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has("lovelace")) {
this._getUnusedEntities();
}
}
protected render(): TemplateResult | void {
if (!this.hass || !this.lovelace) {
return html``;
}
if (this.lovelace.mode === "storage" && this.lovelace.editMode === false) {
return html``;
}
return html`
<ha-card header="Unused entities">
<div class="card-content">
These are the entities that you have available, but are not in your
Lovelace UI yet.
${this.lovelace.mode === "storage"
? html`
<br />Select the entities you want to add to a card and then
click the add card button.
`
: ""}
</div>
<div
class="table-container"
role="table"
aria-label="Unused Entities"
@entity-selection-changed=${this._handleSelectionChanged}
>
<div class="flex-row header" role="rowgroup">
<div class="flex-cell" role="columnheader">Entity</div>
<div class="flex-cell" role="columnheader">Entity id</div>
<div class="flex-cell" role="columnheader">Domain</div>
<div class="flex-cell" role="columnheader">Last Changed</div>
</div>
${this._unusedEntities.map((entity) => {
return html`
<hui-select-row
.selectable=${this.lovelace!.mode === "storage"}
.hass=${this.hass}
.entity=${entity}
></hui-select-row>
`;
})}
</div>
</ha-card>
${this.lovelace.mode === "storage"
? html`
<ha-fab
class="${classMap({
rtl: computeRTL(this.hass),
})}"
icon="hass:plus"
label="${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.add"
)}"
@click="${this._selectView}"
></ha-fab>
`
: ""}
`;
}
private _getUnusedEntities(): void {
if (!this.hass || !this.lovelace) {
return;
}
this._selectedEntities = [];
this._unusedEntities = computeUnusedEntities(this.hass, this._config!);
}
private _handleSelectionChanged(ev: any): void {
if (ev.detail.selected) {
this._selectedEntities.push(ev.detail.entity);
} else {
const index = this._selectedEntities.indexOf(ev.detail.entity);
if (index !== -1) {
this._selectedEntities.splice(index, 1);
}
}
}
private _selectView(): void {
showSelectViewDialog(this, {
lovelace: this.lovelace!,
viewSelectedCallback: (view) => this._addCard(view),
});
}
private _addCard(view: number): void {
showEditCardDialog(this, {
lovelace: this.lovelace!,
path: [view],
entities: this._selectedEntities,
});
}
static get styles(): CSSResult {
return css`
:host {
background: var(--lovelace-background);
padding: 16px;
}
ha-fab {
position: sticky;
float: right;
bottom: 16px;
z-index: 1;
}
ha-fab.rtl {
float: left;
}
div {
box-sizing: border-box;
}
.table-container {
display: block;
margin: auto;
}
.flex-row {
display: flex;
flex-flow: row wrap;
}
.flex-row .flex-cell {
font-weight: bold;
}
.flex-cell {
width: calc(100% / 4);
padding: 12px 24px;
border-bottom: 1px solid #e0e0e0;
vertical-align: middle;
}
@media all and (max-width: 767px) {
.flex-cell {
width: calc(100% / 3);
}
.flex-cell:first-child {
width: 100%;
border-bottom: 0;
}
}
@media all and (max-width: 430px) {
.flex-cell {
border-bottom: 0;
}
.flex-cell:last-child {
border-bottom: 1px solid #e0e0e0;
}
.flex-cell {
width: 100%;
}
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-unused-entities": HuiUnusedEntities;
}
}

View File

@ -116,6 +116,20 @@ class HUIRoot extends LitElement {
@iron-select="${this._deselect}" @iron-select="${this._deselect}"
slot="dropdown-content" slot="dropdown-content"
> >
${__DEMO__ /* No unused entities available in the demo */
? ""
: html`
<paper-item
aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.unused_entities"
)}
@tap="${this._handleUnusedEntities}"
>
${this.hass!.localize(
"ui.panel.lovelace.menu.unused_entities"
)}
</paper-item>
`}
<paper-item @tap="${this.lovelace!.enableFullEditMode}"> <paper-item @tap="${this.lovelace!.enableFullEditMode}">
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.lovelace.editor.menu.raw_editor" "ui.panel.lovelace.editor.menu.raw_editor"
@ -160,11 +174,6 @@ class HUIRoot extends LitElement {
"ui.panel.lovelace.menu.refresh" "ui.panel.lovelace.menu.refresh"
)} )}
</paper-item> </paper-item>
`
: ""}
${__DEMO__ /* No unused entities available in the demo */
? ""
: html`
<paper-item <paper-item
aria-label=${this.hass!.localize( aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.unused_entities" "ui.panel.lovelace.menu.unused_entities"
@ -175,7 +184,8 @@ class HUIRoot extends LitElement {
"ui.panel.lovelace.menu.unused_entities" "ui.panel.lovelace.menu.unused_entities"
)} )}
</paper-item> </paper-item>
`} `
: ""}
<paper-item <paper-item
aria-label=${this.hass!.localize( aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.configure_ui" "ui.panel.lovelace.menu.configure_ui"
@ -422,6 +432,15 @@ class HUIRoot extends LitElement {
} }
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) { if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
// Leave unused entities when leaving edit mode
if (
this.lovelace!.mode === "storage" &&
this._routeData!.view === "hass-unused-entities"
) {
const views = this.config && this.config.views;
navigate(this, `/lovelace/${views[0].path || 0}`);
newSelectView = 0;
}
// On edit mode change, recreate the current view from scratch // On edit mode change, recreate the current view from scratch
force = true; force = true;
// Recalculate to see if we need to adjust content area for tab bar // Recalculate to see if we need to adjust content area for tab bar
@ -562,10 +581,10 @@ class HUIRoot extends LitElement {
if (viewIndex === "hass-unused-entities") { if (viewIndex === "hass-unused-entities") {
const unusedEntities = document.createElement("hui-unused-entities"); const unusedEntities = document.createElement("hui-unused-entities");
// Wait for promise to resolve so that the element has been upgraded. // Wait for promise to resolve so that the element has been upgraded.
import(/* webpackChunkName: "hui-unused-entities" */ "./hui-unused-entities").then( import(/* webpackChunkName: "hui-unused-entities" */ "./editor/unused-entities/hui-unused-entities").then(
() => { () => {
unusedEntities.setConfig(this.config);
unusedEntities.hass = this.hass!; unusedEntities.hass = this.hass!;
unusedEntities.lovelace = this.lovelace!;
} }
); );
if (this.config.background) { if (this.config.background) {

View File

@ -1,112 +0,0 @@
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "./cards/hui-entities-card";
import { computeUnusedEntities } from "./common/compute-unused-entities";
import { createCardElement } from "./common/create-card-element";
import { HomeAssistant } from "../../types";
import { LovelaceCard } from "./types";
import { LovelaceConfig } from "../../data/lovelace";
import computeDomain from "../../common/entity/compute_domain";
export class HuiUnusedEntities extends LitElement {
private _hass?: HomeAssistant;
private _config?: LovelaceConfig;
private _elements?: LovelaceCard[];
static get properties(): PropertyDeclarations {
return {
_hass: {},
_config: {},
};
}
set hass(hass: HomeAssistant) {
this._hass = hass;
if (!this._elements) {
this._createElements();
return;
}
for (const element of this._elements) {
element.hass = this._hass;
}
}
public setConfig(config: LovelaceConfig): void {
this._config = config;
this._createElements();
}
protected render(): TemplateResult | void {
if (!this._config || !this._hass) {
return html``;
}
return html`
${this.renderStyle()}
<div id="root">${this._elements}</div>
`;
}
private renderStyle(): TemplateResult {
return html`
<style>
:host {
background: var(--lovelace-background);
}
#root {
padding: 4px;
display: flex;
flex-wrap: wrap;
}
hui-entities-card {
max-width: 400px;
padding: 4px;
flex: 1 auto;
}
</style>
`;
}
private _createElements(): void {
if (!this._hass) {
return;
}
const domains: { [domain: string]: string[] } = {};
computeUnusedEntities(this._hass, this._config!).forEach((entity) => {
const domain = computeDomain(entity);
if (!(domain in domains)) {
domains[domain] = [];
}
domains[domain].push(entity);
});
this._elements = Object.keys(domains)
.sort()
.map((domain) => {
const el = createCardElement({
type: "entities",
title: this._hass!.localize(`domain.${domain}`) || domain,
entities: domains[domain].map((entity) => ({
entity,
secondary_info: "entity-id",
})),
show_header_toggle: false,
});
el.hass = this._hass;
return el;
});
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-unused-entities": HuiUnusedEntities;
}
}
customElements.define("hui-unused-entities", HuiUnusedEntities);

View File

@ -6,6 +6,7 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "@polymer/paper-fab/paper-fab";
import "../../components/entity/ha-state-label-badge"; import "../../components/entity/ha-state-label-badge";
// This one is for types // This one is for types
// tslint:disable-next-line // tslint:disable-next-line
@ -16,7 +17,6 @@ import applyThemesOnElement from "../../common/dom/apply_themes_on_element";
import { LovelaceViewConfig, LovelaceCardConfig } from "../../data/lovelace"; import { LovelaceViewConfig, LovelaceCardConfig } from "../../data/lovelace";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import { Lovelace, LovelaceCard } from "./types"; import { Lovelace, LovelaceCard } from "./types";
import { createCardElement } from "./common/create-card-element"; import { createCardElement } from "./common/create-card-element";
import { computeCardSize } from "./common/compute-card-size"; import { computeCardSize } from "./common/compute-card-size";

View File

@ -125,6 +125,7 @@ documentContainer.innerHTML = `<custom-style>
/* mwc */ /* mwc */
--mdc-theme-primary: var(--primary-color); --mdc-theme-primary: var(--primary-color);
--mdc-theme-secondary: var(--accent-color);
} }
</style> </style>

239
yarn.lock
View File

@ -726,126 +726,181 @@
dependencies: dependencies:
base64-js "^1.3.0" base64-js "^1.3.0"
"@material/animation@^1.0.0": "@material/animation@^3.1.0":
version "1.0.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/animation/-/animation-1.0.0.tgz#dfd8575c8b031203917dc838ac0e3c0fe0f6709b" resolved "https://registry.yarnpkg.com/@material/animation/-/animation-3.1.0.tgz#ab04e7c2e92ab370a2b28d12af1b88538d23014f"
integrity sha512-Ed5/vggn6ZhSJ87yn3ZS1d826VJNFz73jHF2bSsgRtHDoB8KCuOwQMfdgAgDa4lKDF6CDIPCKBZPKrs2ubehdw== integrity sha512-ZfP95awrPBLhpaCTPNx+xKYPp2D88fzf5p5YNVp6diUAGRpq3g12Aq7qJfIHDXAFo5CtrYhgOKRqIKxUVcFisQ==
dependencies: dependencies:
tslib "^1.9.3" tslib "^1.9.3"
"@material/base@^1.0.0": "@material/base@^3.0.0", "@material/base@^3.1.0":
version "1.0.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/base/-/base-1.0.0.tgz#e4ef0b22c54aa887af94f5988fb1c0cb3245beba" resolved "https://registry.yarnpkg.com/@material/base/-/base-3.1.0.tgz#ab933896ab6390d4e1d2f9797b74a90f47db272a"
integrity sha512-5dxFp46x5FA+Epg6YHLzN+5zRt9S2wR84UdvVAEJ1egea94m9UHUg7y9tAnNSN16aexRSywmzyLwPr+i8PGEYA== integrity sha512-pWEBHyPrMV3rdnjqWWH96h5t3MxQI6V1J9jOor+UBG7bXQtr6InTabTqhz5CLY7r+qZU8YvNh2OKIy8heP0cyQ==
dependencies: dependencies:
tslib "^1.9.3" tslib "^1.9.3"
"@material/button@^1.0.0": "@material/button@^3.0.0":
version "1.1.1" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/button/-/button-1.1.1.tgz#35af0d295f6ce6e3df9d4978485b5294d794f682" resolved "https://registry.yarnpkg.com/@material/button/-/button-3.1.0.tgz#bee60a7a5ec0a7c3dcb242dac48bbdaf16bac66b"
integrity sha512-03aEyzZBIeqpgZkqLjro/enz8ORUnfQslBUdAgkPqdjh1X0oIEugr3UaFyC5QlSBTU3j3GIsnKIxWaggkRenpQ== integrity sha512-DBx3UYtvh8/Vn8/2oDrwrpkyGQrZCElcbkJy8CnZtftbb1P5qVKGvXqEeoND3d0AWTIwdKDYbX3X0zSXQUe3iA==
dependencies: dependencies:
"@material/elevation" "^1.1.0" "@material/elevation" "^3.1.0"
"@material/feature-targeting" "^0.44.1" "@material/feature-targeting" "^3.1.0"
"@material/ripple" "^1.1.0" "@material/ripple" "^3.1.0"
"@material/rtl" "^0.42.0" "@material/rtl" "^3.1.0"
"@material/shape" "^1.1.1" "@material/shape" "^3.1.0"
"@material/theme" "^1.1.0" "@material/theme" "^3.1.0"
"@material/typography" "^1.0.0" "@material/typography" "^3.1.0"
"@material/dom@^1.1.0": "@material/checkbox@^3.0.0":
version "1.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/dom/-/dom-1.1.0.tgz#3bd3d1a3415b4181118fecb182d93beda56a6f8c" resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-3.1.0.tgz#bb8eadda0d260e75e8a7479418490eec846a8520"
integrity sha512-+HWW38ZaM2UBPu4+7QCusLDSf4tFT31rsEXHkTkxYSg/QpDivfPx6YDz4OmYtafmhPR1d1YjqB3MYysUHdodyw== integrity sha512-Rcv6Srj2p3MTsODPLJLgRzGW142ovQTKblkCy9AxABZriQUPRCV/fkJwB0LlqecHgubhnjhtj2Zui0o9jhfu/w==
dependencies:
"@material/animation" "^3.1.0"
"@material/base" "^3.1.0"
"@material/dom" "^3.1.0"
"@material/feature-targeting" "^3.1.0"
"@material/ripple" "^3.1.0"
"@material/rtl" "^3.1.0"
"@material/theme" "^3.1.0"
tslib "^1.9.3"
"@material/dom@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/dom/-/dom-3.1.0.tgz#1e58cad0cd5e1d9d6f6cb07422e327ad34a9d405"
integrity sha512-RtBLSkrBjMfHwknaGBifAIC8cBWF9pXjz2IYqfI2braB6SfQI4vhdJviwyiA5BmA/psn3cKpBUZbHI0ym0O0SQ==
dependencies: dependencies:
tslib "^1.9.3" tslib "^1.9.3"
"@material/elevation@^1.1.0": "@material/elevation@^3.1.0":
version "1.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-1.1.0.tgz#def23c360ae067b43c1632a331b9883b9f679cc5" resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-3.1.0.tgz#2fcbde47653cc115519a39acc2d04b34e4cff12b"
integrity sha512-m4eATJvDhWK1BT+yA1iHz5mhAk8cV9olC4mjVzm4PTAqhDH2yya4WzjN1HPVHE/a65ObyZ7V4qopxu9MRocm3A== integrity sha512-e45LqiG6LfbR1M52YkSLA7pQPeYJOuOVzLp27xy2072TnLuJexMxhEaG4O1novEIjsTtMjqfrfJ/koODU5vEew==
dependencies: dependencies:
"@material/animation" "^1.0.0" "@material/animation" "^3.1.0"
"@material/feature-targeting" "^0.44.1" "@material/feature-targeting" "^3.1.0"
"@material/theme" "^1.1.0" "@material/theme" "^3.1.0"
"@material/feature-targeting@^0.44.1": "@material/fab@^3.0.0":
version "0.44.1" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-0.44.1.tgz#afafc80294e5efab94bee31a187273d43d34979a" resolved "https://registry.yarnpkg.com/@material/fab/-/fab-3.1.0.tgz#2ea42ee0e351b94f8ce2510bd292f7f9d05ef6eb"
integrity sha512-90cc7njn4aHbH9UxY8qgZth1W5JgOgcEdWdubH1t7sFkwqFxS5g3zgxSBt46TygFBVIXNZNq35Xmg80wgqO7Pg== integrity sha512-/mYsi9u/N7m9XlRR7tyCEh3WlYjrUqKS9FjxTdB08r6v04bTK5G7XTAtnvPrW47QCVWV0iL3FM2iwO0sVXqzZg==
"@material/mwc-base@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@material/mwc-base/-/mwc-base-0.6.0.tgz#2077b5f94c3d8fa2a65736b02c3d380314ea1154"
integrity sha512-AiMEoU3dkLhuEnK5HJ0yMrdcyq5rUq9LdooxOSLMzPRr/ALT8YS14/oklufYiPagehzJcL0MeiyL40OlSpkyBA==
dependencies: dependencies:
"@material/base" "^1.0.0" "@material/animation" "^3.1.0"
lit-element "^2.0.1" "@material/elevation" "^3.1.0"
"@material/feature-targeting" "^3.1.0"
"@material/ripple" "^3.1.0"
"@material/rtl" "^3.1.0"
"@material/shape" "^3.1.0"
"@material/theme" "^3.1.0"
"@material/typography" "^3.1.0"
"@material/feature-targeting@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-3.1.0.tgz#a6f6cacd1e0c9b60dc82f2f3bb2de8cfd472252b"
integrity sha512-aXAa1Pv6w32URacE9LfMsl9zI6hFwx1K0Lp3Xpyf4rAkmaAB6z0gOkhicOrVFc0f64YheJgHjE7hJFieVenQdw==
"@material/mwc-base@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@material/mwc-base/-/mwc-base-0.8.0.tgz#35b8c6e73feb9c262272f723ed0c271939997365"
integrity sha512-9qyC6eOKduzHaKmo6p5x53OAw2+mCB4t5roesqYiOUL8lJ8ZVssDczQvzSAKOcRd4JY8RyTgtpJFjBrScmWz8g==
dependencies:
"@material/base" "^3.0.0"
"@material/dom" "^3.1.0"
lit-element "^2.2.1"
lit-html "^1.0.0" lit-html "^1.0.0"
tslib "^1.10.0"
"@material/mwc-button@^0.6.0": "@material/mwc-button@^0.8.0":
version "0.6.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/@material/mwc-button/-/mwc-button-0.6.0.tgz#5264623ab7bfc80cc0a118ae9188a2c69b4a8e8c" resolved "https://registry.yarnpkg.com/@material/mwc-button/-/mwc-button-0.8.0.tgz#32576f27383e9f60c3eedfa1e0a3b6c6ab85529a"
integrity sha512-oZTXXtg5z7tqvbFN5gMWsya/OU1ThEQ8ZZ/KN4PzDHGoYcjGLWWYTyDtarfP2VfJn+pRL0Ql5+l3i8j1i4Vr8Q== integrity sha512-f4BelOfCFVlNJuNQuO9nImYv21STPg4iStrzG7N11ptACFj6TxuOR9ghT9xAHHIL26RIySGq/N+eAy9nuFynIQ==
dependencies: dependencies:
"@material/button" "^1.0.0" "@material/button" "^3.0.0"
"@material/mwc-base" "^0.6.0" "@material/mwc-base" "^0.8.0"
"@material/mwc-icon" "^0.6.0" "@material/mwc-icon" "^0.8.0"
"@material/mwc-ripple" "^0.6.0" "@material/mwc-ripple" "^0.8.0"
tslib "^1.10.0"
"@material/mwc-icon@^0.6.0": "@material/mwc-checkbox@^0.8.0":
version "0.6.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/@material/mwc-icon/-/mwc-icon-0.6.0.tgz#67a71d95aa9a6d2379c5896673c2d0f1f2666e4e" resolved "https://registry.yarnpkg.com/@material/mwc-checkbox/-/mwc-checkbox-0.8.0.tgz#83105189c6fccca69e2342c27540bc23d910a15c"
integrity sha512-Pm4nalbSfsgMz0K8dRaE2tBsyiCozrlgh9iEtBvRdYV6IzPJacXjqsf8+2faW3lfmh4PRLQzVJ7Fldm+ljxzBA== integrity sha512-5qRfoON6FttQoAeTrzI00ODlO+k8dTAREKtZs4cpP3eM0KQqajS4ZaOJhtIa/ew/8XKoEShoSgRaEEtFbR91Fw==
dependencies: dependencies:
"@material/mwc-base" "^0.6.0" "@material/checkbox" "^3.0.0"
"@material/mwc-base" "^0.8.0"
"@material/mwc-ripple" "^0.8.0"
tslib "^1.10.0"
"@material/mwc-ripple@^0.6.0": "@material/mwc-fab@^0.8.0":
version "0.6.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/@material/mwc-ripple/-/mwc-ripple-0.6.0.tgz#602eb1855acd7e02d79398290ff223f9335976e3" resolved "https://registry.yarnpkg.com/@material/mwc-fab/-/mwc-fab-0.8.0.tgz#8e9afac962789bd98e2417712092f1358f355531"
integrity sha512-K0b3VtKTlUd2RLaSJd6y9lBX47A84QjsK4eMn3PhDlWG7CkfhRf5XBZrOf/wzrqNf2/0w5of+8rFkohTraLHiw== integrity sha512-IzRIYX9Xze9cc4CWGrz2Ncg9U5PmDTyKFVcHwGmyM0UQ2Il8gQxrpt7DxiDQT2gutKhlr+nHo71fQifk6Zi2mQ==
dependencies: dependencies:
"@material/mwc-base" "^0.6.0" "@material/fab" "^3.0.0"
"@material/ripple" "^1.0.0" "@material/mwc-base" "^0.8.0"
"@material/mwc-icon" "^0.8.0"
"@material/mwc-ripple" "^0.8.0"
tslib "^1.10.0"
"@material/mwc-icon@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@material/mwc-icon/-/mwc-icon-0.8.0.tgz#6dd04def560f1147be1f6d1b0b45a26e1199a97c"
integrity sha512-jhSol1nV1KzxTXm82q16/Rzfy8epmfLehL2L0+mOCGvka4VUL/GJkjfbaKJMKwQWjXAgaOzHX7D9vVyr3EA5wg==
dependencies:
"@material/mwc-base" "^0.8.0"
tslib "^1.10.0"
"@material/mwc-ripple@0.8.0", "@material/mwc-ripple@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@material/mwc-ripple/-/mwc-ripple-0.8.0.tgz#a18e43a087e4356de8740d082378d58a166aa93c"
integrity sha512-hJL+8xNunE+GUk+dtgeIVL9BJM5QPl5uyIufxzGEbVu+pmUfVDml+3HQLapO6Q5MQZMZpO4tDNwJNx9HOAo5KQ==
dependencies:
"@material/dom" "^3.1.0"
"@material/mwc-base" "^0.8.0"
"@material/ripple" "^3.0.0"
lit-html "^1.0.0" lit-html "^1.0.0"
tslib "^1.10.0"
"@material/ripple@^1.0.0", "@material/ripple@^1.1.0": "@material/ripple@^3.0.0", "@material/ripple@^3.1.0":
version "1.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-1.1.0.tgz#236016fb30c8366faf143297df2c38166d84ffbc" resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-3.1.0.tgz#5cb581e9a70415c50c8b92ecd8628d5eeae34c74"
integrity sha512-mkfDBZAmxjpRG7V9TrfOmLxt1g/wvGHCXtYPgvH7W8ozjf53edqxLOFENEdvHbie27y9nyixzXn0gzU0HnxSeA== integrity sha512-mYvd2iWbQyVd6aLS9alHShoL05p/D0cvh5h1ga3atz55azooMLhGsbbE1YlEqUDKHKNuNvdVFm+0IfWdvvRgsw==
dependencies: dependencies:
"@material/animation" "^1.0.0" "@material/animation" "^3.1.0"
"@material/base" "^1.0.0" "@material/base" "^3.1.0"
"@material/dom" "^1.1.0" "@material/dom" "^3.1.0"
"@material/feature-targeting" "^0.44.1" "@material/feature-targeting" "^3.1.0"
"@material/theme" "^1.1.0" "@material/theme" "^3.1.0"
tslib "^1.9.3" tslib "^1.9.3"
"@material/rtl@^0.42.0": "@material/rtl@^3.1.0":
version "0.42.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-0.42.0.tgz#1836e78186c2d8b996f6fbf97adab203535335bc" resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-3.1.0.tgz#8a5254bcf6c4d897e16206d52ba98b8eb98d45b7"
integrity sha512-VrnrKJzhmspsN8WXHuxxBZ69yM5IwhCUqWr1t1eNfw3ZEvEj7i1g3P31HGowKThIN1dc1Wh4LE14rCISWCtv5w== integrity sha512-HH19edQNb139zC+1SZ6/C9G92E54fUrnnW9AAF7t5eGjGdF26YJXJ/uhz+TnFhNUMi/QGrKUSycd4o73nU1m4A==
"@material/shape@^1.1.1": "@material/shape@^3.1.0":
version "1.1.1" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/shape/-/shape-1.1.1.tgz#7a5368694bc3555e69ea547950904b46fa1024bf" resolved "https://registry.yarnpkg.com/@material/shape/-/shape-3.1.0.tgz#7ccac6606d0ae45b779b3e52b8921c09c2b2f429"
integrity sha512-Jge/h1XBLjdLlam4QMSzVgM99e/N8+elQROPkltqVP7eyLc17BwM3aP5cLVfZDgrJgvsjUxbgAP1H1j8sqmUyg== integrity sha512-Oyvs7YjHfByA0e9IVVp7ojAlPwgSu3Bl0cioiE0OdkidkAaNu0izM2ryRzMBDH5o8+lRD0kpZoT+9CVVCdaYIg==
dependencies: dependencies:
"@material/feature-targeting" "^0.44.1" "@material/feature-targeting" "^3.1.0"
"@material/theme@^1.1.0": "@material/theme@^3.1.0":
version "1.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/theme/-/theme-1.1.0.tgz#9c95dd804168c23c30589fcf09ecc5af5b3d1adc" resolved "https://registry.yarnpkg.com/@material/theme/-/theme-3.1.0.tgz#d31147bbc9e20bdaa3e322c9e898e4fc98807d8c"
integrity sha512-YYUV9Rhbx4r/EMb/zoOYJUWjhXChNaLlH1rqt3vpNVyxRcxGqoVMGp5u1XALBCFiD9dACPKLIkKyRYa928nmPQ== integrity sha512-N4JX+akOwg1faAvFvIEhDcwW4cZfUpwEn8lct6Vs3WczjLF6/KdIoLVaYh+eVl1bzfsoIuWvx56j0B1PjXZw9g==
dependencies: dependencies:
"@material/feature-targeting" "^0.44.1" "@material/feature-targeting" "^3.1.0"
"@material/typography@^1.0.0": "@material/typography@^3.1.0":
version "1.0.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/typography/-/typography-1.0.0.tgz#327ecfcac5ee3af8a3a102f3f125a761202f4189" resolved "https://registry.yarnpkg.com/@material/typography/-/typography-3.1.0.tgz#5a3aee31f49f6b8c87ebc91b77c5b896b280c492"
integrity sha512-Oeqbjci1cC7jTE8/n3dwnkqKe9ZeWiaE+rgMtRYtRFw1HvAw14SpGA5EEAS/Li2Hu2KZ50FYCe3HYqShfxtChA== integrity sha512-aSNBQvVxIH1kORSYdLGuSTivx6oJ1MSOSTUAsUwhXPQLQlvbdFeZaqUp7xgn+EvRsHGRFhWk5YGuiBds9+7zQg==
dependencies: dependencies:
"@material/feature-targeting" "^0.44.1" "@material/feature-targeting" "^3.1.0"
"@mdi/svg@4.3.95": "@mdi/svg@4.3.95":
version "4.3.95" version "4.3.95"
@ -8590,10 +8645,10 @@ listr@^0.14.2:
p-map "^2.0.0" p-map "^2.0.0"
rxjs "^6.3.3" rxjs "^6.3.3"
lit-element@^2.0.1, lit-element@^2.2.0: lit-element@^2.2.1:
version "2.2.0" version "2.2.1"
resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.2.0.tgz#e853be38021f0c7743a10180affdf84b8a02400c" resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.2.1.tgz#79c94d8cfdc2d73b245656e37991bd1e4811d96f"
integrity sha512-Mzs3H7IO4wAnpzqreHw6dQqp9IG+h/oN8X9pgNbMZbE7x6B0aNOwP5Nveox/5HE+65ZfW2PeULEjoHkrwpTnuQ== integrity sha512-ipDcgQ1EpW6Va2Z6dWm79jYdimVepO5GL0eYkZrFvdr0OD/1N260Q9DH+K5HXHFrRoC7dOg+ZpED2XE0TgGdXw==
dependencies: dependencies:
lit-html "^1.0.0" lit-html "^1.0.0"