Card Picker: Entity Picker (#6693)

This commit is contained in:
Zack Arnett 2020-09-03 18:28:53 -05:00 committed by GitHub
parent 1431e75f8b
commit f3639c2663
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 780 additions and 414 deletions

View File

@ -1,4 +1,7 @@
import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import memoizeOne from "memoize-one";
import { import {
css, css,
CSSResult, CSSResult,
@ -11,30 +14,35 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import { styleMap } from "lit-html/directives/style-map";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../common/search/search-input";
import { UNAVAILABLE_STATES } from "../../../../data/entity"; import { UNAVAILABLE_STATES } from "../../../../data/entity";
import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
import { import {
CustomCardEntry, CustomCardEntry,
customCards, customCards,
CUSTOM_TYPE_PREFIX, CUSTOM_TYPE_PREFIX,
getCustomCardEntry, getCustomCardEntry,
} from "../../../../data/lovelace_custom_cards"; } from "../../../../data/lovelace_custom_cards";
import { HomeAssistant } from "../../../../types";
import { import {
calcUnusedEntities,
computeUsedEntities, computeUsedEntities,
calcUnusedEntities,
} from "../../common/compute-unused-entities"; } from "../../common/compute-unused-entities";
import { tryCreateCardElement } from "../../create-element/create-card-element"; import { tryCreateCardElement } from "../../create-element/create-card-element";
import { LovelaceCard } from "../../types";
import { getCardStubConfig } from "../get-card-stub-config"; import { getCardStubConfig } from "../get-card-stub-config";
import { CardPickTarget, Card } from "../types";
import { coreCards } from "../lovelace-cards"; import { coreCards } from "../lovelace-cards";
import { styleMap } from "lit-html/directives/style-map";
import type { CardPickTarget, Card } from "../types";
import type { LovelaceCard } from "../../types";
import type { HomeAssistant } from "../../../../types";
import type {
LovelaceCardConfig,
LovelaceConfig,
} from "../../../../data/lovelace";
import "../../../../components/ha-circular-progress"; import "../../../../components/ha-circular-progress";
import "../../../../common/search/search-input";
interface CardElement { interface CardElement {
card: Card; card: Card;
@ -53,14 +61,14 @@ export class HuiCardPicker extends LitElement {
@internalProperty() private _filter = ""; @internalProperty() private _filter = "";
private _unusedEntities?: string[];
private _usedEntities?: string[];
@internalProperty() private _width?: number; @internalProperty() private _width?: number;
@internalProperty() private _height?: number; @internalProperty() private _height?: number;
private _unusedEntities?: string[];
private _usedEntities?: string[];
private _filterCards = memoizeOne( private _filterCards = memoizeOne(
(cardElements: CardElement[], filter?: string): CardElement[] => { (cardElements: CardElement[], filter?: string): CardElement[] => {
if (!filter) { if (!filter) {
@ -232,85 +240,6 @@ export class HuiCardPicker extends LitElement {
this._filter = value; this._filter = value;
} }
static get styles(): CSSResult[] {
return [
css`
.cards-container {
display: grid;
grid-gap: 8px 8px;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
margin-top: 20px;
}
.card {
height: 100%;
max-width: 500px;
display: flex;
flex-direction: column;
border-radius: 4px;
border: 1px solid var(--divider-color);
background: var(--primary-background-color, #fafafa);
cursor: pointer;
box-sizing: border-box;
position: relative;
}
.card-header {
color: var(--ha-card-header-color, --primary-text-color);
font-family: var(--ha-card-header-font-family, inherit);
font-size: 16px;
font-weight: bold;
letter-spacing: -0.012em;
line-height: 20px;
padding: 12px 16px;
display: block;
text-align: center;
background: var(
--ha-card-background,
var(--card-background-color, white)
);
border-radius: 0 0 4px 4px;
border-bottom: 1px solid var(--divider-color);
}
.preview {
pointer-events: none;
margin: 20px;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
}
.preview > :first-child {
zoom: 0.6;
display: block;
width: 100%;
}
.description {
text-align: center;
}
.spinner {
align-items: center;
justify-content: center;
}
.overlay {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
.manual {
max-width: none;
}
`,
];
}
private _cardPicked(ev: Event): void { private _cardPicked(ev: Event): void {
const config: LovelaceCardConfig = (ev.currentTarget! as CardPickTarget) const config: LovelaceCardConfig = (ev.currentTarget! as CardPickTarget)
.config; .config;
@ -406,6 +335,85 @@ export class HuiCardPicker extends LitElement {
</div> </div>
`; `;
} }
static get styles(): CSSResult[] {
return [
css`
.cards-container {
display: grid;
grid-gap: 8px 8px;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
margin-top: 20px;
}
.card {
height: 100%;
max-width: 500px;
display: flex;
flex-direction: column;
border-radius: 4px;
border: 1px solid var(--divider-color);
background: var(--primary-background-color, #fafafa);
cursor: pointer;
box-sizing: border-box;
position: relative;
}
.card-header {
color: var(--ha-card-header-color, --primary-text-color);
font-family: var(--ha-card-header-font-family, inherit);
font-size: 16px;
font-weight: bold;
letter-spacing: -0.012em;
line-height: 20px;
padding: 12px 16px;
display: block;
text-align: center;
background: var(
--ha-card-background,
var(--card-background-color, white)
);
border-radius: 0 0 4px 4px;
border-bottom: 1px solid var(--divider-color);
}
.preview {
pointer-events: none;
margin: 20px;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
}
.preview > :first-child {
zoom: 0.6;
display: block;
width: 100%;
}
.description {
text-align: center;
}
.spinner {
align-items: center;
justify-content: center;
}
.overlay {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
.manual {
max-width: none;
}
`,
];
}
} }
declare global { declare global {

View File

@ -0,0 +1,278 @@
import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab";
import {
css,
CSSResultArray,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoize from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { DataTableRowData } from "../../../../components/data-table/ha-data-table";
import "../../../../components/ha-dialog";
import "../../../../components/ha-header-bar";
import type { LovelaceViewConfig } from "../../../../data/lovelace";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "./hui-card-picker";
import "./hui-entity-picker-table";
import {
EditCardDialogParams,
showEditCardDialog,
} from "./show-edit-card-dialog";
import { showSuggestCardDialog } from "./show-suggest-card-dialog";
declare global {
interface HASSDomEvents {
"selected-changed": SelectedChangedEvent;
}
}
interface SelectedChangedEvent {
selectedEntities: string[];
}
@customElement("hui-dialog-create-card")
export class HuiCreateDialogCard extends LitElement implements HassDialog {
@property({ attribute: false }) protected hass!: HomeAssistant;
@internalProperty() private _params?: EditCardDialogParams;
@internalProperty() private _viewConfig!: LovelaceViewConfig;
@internalProperty() private _selectedEntities: string[] = [];
@internalProperty() private _currTabIndex = 0;
public async showDialog(params: EditCardDialogParams): Promise<void> {
this._params = params;
const [view] = params.path;
this._viewConfig = params.lovelaceConfig.views[view];
}
public closeDialog(): boolean {
this._params = undefined;
this._currTabIndex = 0;
fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
}
protected render(): TemplateResult {
if (!this._params) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@keydown=${this._ignoreKeydown}
@closed=${this._cancel}
.heading=${true}
>
<div slot="heading">
<ha-header-bar>
<div slot="title">
${this._viewConfig.title
? this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.pick_card_view_title",
"name",
`"${this._viewConfig.title}"`
)
: this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.pick_card"
)}
</div>
</ha-header-bar>
<mwc-tab-bar
.activeIndex=${this._currTabIndex}
@MDCTabBar:activated=${(ev: CustomEvent) =>
this._handleTabChanged(ev)}
>
<mwc-tab
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.cardpicker.by_card"
)}
></mwc-tab>
<mwc-tab
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.cardpicker.by_entity"
)}
></mwc-tab>
</mwc-tab-bar>
</div>
${this._currTabIndex === 0
? html`
<hui-card-picker
.lovelace=${this._params.lovelaceConfig}
.hass=${this.hass}
@config-changed=${this._handleCardPicked}
></hui-card-picker>
`
: html`
<div class="entity-picker-container">
<hui-entity-picker-table
.hass=${this.hass}
.narrow=${true}
.entities=${this._allEntities(this.hass.states)}
@selected-changed=${this._handleSelectedChanged}
></hui-entity-picker-table>
</div>
`}
<div slot="primaryAction">
<mwc-button @click=${this._cancel}>
${this.hass!.localize("ui.common.cancel")}
</mwc-button>
${this._selectedEntities.length
? html`
<mwc-button @click=${this._suggestCards}>
${this.hass!.localize("ui.common.continue")}
</mwc-button>
`
: ""}
</div>
</ha-dialog>
`;
}
private _ignoreKeydown(ev: KeyboardEvent) {
ev.stopPropagation();
}
static get styles(): CSSResultArray {
return [
haStyleDialog,
css`
@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%;
height: 100%;
}
}
@media all and (min-width: 850px) {
ha-dialog {
--mdc-dialog-min-width: 845px;
}
}
ha-dialog {
--mdc-dialog-max-width: 845px;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
border-bottom: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
@media (min-width: 1200px) {
ha-dialog {
--mdc-dialog-max-width: calc(100% - 32px);
--mdc-dialog-min-width: 1000px;
}
}
.header_button {
color: inherit;
text-decoration: none;
}
mwc-tab-bar {
padding-top: 8px;
}
.entity-picker-container {
display: flex;
flex-direction: column;
height: 100%;
min-height: calc(100vh - 112px);
margin-top: -20px;
}
`,
];
}
private _handleCardPicked(ev) {
const config = ev.detail.config;
if (this._params!.entities && this._params!.entities.length) {
if (Object.keys(config).includes("entities")) {
config.entities = this._params!.entities;
} else if (Object.keys(config).includes("entity")) {
config.entity = this._params!.entities[0];
}
}
showEditCardDialog(this, {
lovelaceConfig: this._params!.lovelaceConfig,
saveConfig: this._params!.saveConfig,
path: this._params!.path,
cardConfig: config,
});
this.closeDialog();
}
private _handleTabChanged(ev: CustomEvent): void {
const newTab = ev.detail.index;
if (newTab === this._currTabIndex) {
return;
}
this._currTabIndex = ev.detail.index;
this._selectedEntities = [];
}
private _handleSelectedChanged(ev: CustomEvent): void {
this._selectedEntities = ev.detail.selectedEntities;
}
private _cancel(ev?: Event) {
if (ev) {
ev.stopPropagation();
}
this.closeDialog();
}
private _suggestCards(): void {
showSuggestCardDialog(this, {
lovelaceConfig: this._params!.lovelaceConfig,
saveConfig: this._params!.saveConfig,
path: this._params!.path as [number],
entities: this._selectedEntities,
});
this.closeDialog();
}
private _allEntities = memoize((entities) =>
Object.keys(entities).map((entity) => {
const stateObj = this.hass.states[entity];
return {
icon: "",
entity_id: entity,
stateObj,
name: computeStateName(stateObj),
domain: computeDomain(entity),
last_changed: stateObj!.last_changed,
} as DataTableRowData;
})
);
}
declare global {
interface HTMLElementTagNameMap {
"hui-dialog-create-card": HuiCreateDialogCard;
}
}

View File

@ -11,28 +11,32 @@ import {
TemplateResult, TemplateResult,
PropertyValues, PropertyValues,
} from "lit-element"; } from "lit-element";
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; import { mdiHelpCircle } from "@mdi/js";
import "../../../../components/ha-dialog";
import { fireEvent } from "../../../../common/dom/fire_event";
import { haStyleDialog } from "../../../../resources/styles";
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
import { addCard, replaceCard } from "../config-util";
import { getCardDocumentationURL } from "../get-card-documentation-url";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types";
import type { GUIModeChangedEvent } from "../types";
import type { ConfigChangedEvent, HuiCardEditor } from "./hui-card-editor";
import type { EditCardDialogParams } from "./show-edit-card-dialog";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import type { import type {
LovelaceCardConfig, LovelaceCardConfig,
LovelaceViewConfig, LovelaceViewConfig,
} from "../../../../data/lovelace"; } from "../../../../data/lovelace";
import { haStyleDialog } from "../../../../resources/styles";
import "../../../../components/ha-circular-progress";
import type { HomeAssistant } from "../../../../types";
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
import { addCard, replaceCard } from "../config-util";
import type { GUIModeChangedEvent } from "../types";
import "./hui-card-editor"; import "./hui-card-editor";
import type { ConfigChangedEvent, HuiCardEditor } from "./hui-card-editor";
import "./hui-card-picker";
import "./hui-card-preview"; import "./hui-card-preview";
import type { EditCardDialogParams } from "./show-edit-card-dialog"; import "../../../../components/ha-dialog";
import { getCardDocumentationURL } from "../get-card-documentation-url"; import "../../../../components/ha-header-bar";
import { mdiHelpCircle } from "@mdi/js"; import "../../../../components/ha-circular-progress";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
declare global { declare global {
// for fire event // for fire event
@ -47,7 +51,7 @@ declare global {
@customElement("hui-dialog-edit-card") @customElement("hui-dialog-edit-card")
export class HuiDialogEditCard extends LitElement implements HassDialog { export class HuiDialogEditCard extends LitElement implements HassDialog {
@property() protected hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private _params?: EditCardDialogParams; @internalProperty() private _params?: EditCardDialogParams;
@ -150,10 +154,15 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
@keydown=${this._ignoreKeydown} @keydown=${this._ignoreKeydown}
@closed=${this._cancel} @closed=${this._cancel}
@opened=${this._opened} @opened=${this._opened}
.heading=${html`${heading} .heading=${true}
>
<div slot="heading">
<ha-header-bar>
<div slot="title">${heading}</div>
${this._documentationURL !== undefined ${this._documentationURL !== undefined
? html` ? html`
<a <a
slot="actionItems"
class="header_button" class="header_button"
href=${this._documentationURL} href=${this._documentationURL}
title=${this.hass!.localize("ui.panel.lovelace.menu.help")} title=${this.hass!.localize("ui.panel.lovelace.menu.help")}
@ -166,18 +175,9 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
</mwc-icon-button> </mwc-icon-button>
</a> </a>
` `
: ""}`} : ""}
> </ha-header-bar>
<div> </div>
${this._cardConfig === undefined
? html`
<hui-card-picker
.lovelace=${this._params.lovelaceConfig}
.hass=${this.hass}
@config-changed=${this._handleCardPicked}
></hui-card-picker>
`
: html`
<div class="content"> <div class="content">
<div class="element-editor"> <div class="element-editor">
<hui-card-editor <hui-card-editor
@ -205,8 +205,6 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
: ``} : ``}
</div> </div>
</div> </div>
`}
</div>
${this._cardConfig !== undefined ${this._cardConfig !== undefined
? html` ? html`
<mwc-button <mwc-button
@ -256,126 +254,6 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
ev.stopPropagation(); ev.stopPropagation();
} }
static get styles(): CSSResultArray {
return [
haStyleDialog,
css`
:host {
--code-mirror-max-height: calc(100vh - 176px);
}
@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%;
height: 100%;
}
}
@media all and (min-width: 850px) {
ha-dialog {
--mdc-dialog-min-width: 845px;
}
}
ha-dialog {
--mdc-dialog-max-width: 845px;
}
.center {
margin-left: auto;
margin-right: auto;
}
.content {
display: flex;
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;
}
.content {
flex-direction: row;
}
.content > * {
flex-basis: 0;
flex-grow: 1;
flex-shrink: 1;
min-width: 0;
}
.content hui-card-preview {
padding: 8px 10px;
margin: auto 0px;
max-width: 500px;
}
}
mwc-button ha-circular-progress {
margin-right: 20px;
}
.hidden {
display: none;
}
.element-editor {
margin-bottom: 8px;
}
.blur {
filter: blur(2px) grayscale(100%);
}
.element-preview {
position: relative;
}
.element-preview ha-circular-progress {
top: 50%;
left: 50%;
position: absolute;
z-index: 10;
}
hui-card-preview {
padding-top: 8px;
margin-bottom: 4px;
display: block;
width: 100%;
box-sizing: border-box;
}
.gui-mode-button {
margin-right: auto;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
`,
];
}
private _handleCardPicked(ev) {
const config = ev.detail.config;
if (this._params!.entities && this._params!.entities.length) {
if (Object.keys(config).includes("entities")) {
config.entities = this._params!.entities;
} else if (Object.keys(config).includes("entity")) {
config.entity = this._params!.entities[0];
}
}
this._cardConfig = deepFreeze(config);
this._error = ev.detail.error;
this._dirty = true;
}
private _handleConfigChanged(ev: HASSDomEvent<ConfigChangedEvent>) { private _handleConfigChanged(ev: HASSDomEvent<ConfigChangedEvent>) {
this._cardConfig = deepFreeze(ev.detail.config); this._cardConfig = deepFreeze(ev.detail.config);
this._error = ev.detail.error; this._error = ev.detail.error;
@ -463,6 +341,124 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
showSaveSuccessToast(this, this.hass); showSaveSuccessToast(this, this.hass);
this.closeDialog(); this.closeDialog();
} }
static get styles(): CSSResultArray {
return [
haStyleDialog,
css`
:host {
--code-mirror-max-height: calc(100vh - 176px);
}
@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%;
height: 100%;
}
}
@media all and (min-width: 850px) {
ha-dialog {
--mdc-dialog-min-width: 845px;
}
}
ha-dialog {
--mdc-dialog-max-width: 845px;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
border-bottom: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
.center {
margin-left: auto;
margin-right: auto;
}
.content {
display: flex;
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;
}
.content {
flex-direction: row;
}
.content > * {
flex-basis: 0;
flex-grow: 1;
flex-shrink: 1;
min-width: 0;
}
.content hui-card-preview {
padding: 8px 10px;
margin: auto 0px;
max-width: 500px;
}
}
mwc-button ha-circular-progress {
margin-right: 20px;
}
.hidden {
display: none;
}
.element-editor {
margin-bottom: 8px;
}
.blur {
filter: blur(2px) grayscale(100%);
}
.element-preview {
position: relative;
}
.element-preview ha-circular-progress {
top: 50%;
left: 50%;
position: absolute;
z-index: 10;
}
hui-card-preview {
padding-top: 8px;
margin-bottom: 4px;
display: block;
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;
}
`,
];
}
} }
declare global { declare global {

View File

@ -5,9 +5,9 @@ import {
CSSResultArray, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
query, query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
@ -21,7 +21,7 @@ import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
import { computeCards } from "../../common/generate-lovelace-config"; import { computeCards } from "../../common/generate-lovelace-config";
import { addCards } from "../config-util"; import { addCards } from "../config-util";
import "./hui-card-preview"; import "./hui-card-preview";
import { showEditCardDialog } from "./show-edit-card-dialog"; import { showCreateCardDialog } from "./show-create-card-dialog";
import { SuggestCardDialogParams } from "./show-suggest-card-dialog"; import { SuggestCardDialogParams } from "./show-suggest-card-dialog";
@customElement("hui-dialog-suggest-card") @customElement("hui-dialog-suggest-card")
@ -179,7 +179,8 @@ export class HuiDialogSuggestCard extends LitElement {
) { ) {
return; return;
} }
showEditCardDialog(this, {
showCreateCardDialog(this, {
lovelaceConfig: this._params!.lovelaceConfig, lovelaceConfig: this._params!.lovelaceConfig,
saveConfig: this._params!.saveConfig, saveConfig: this._params!.saveConfig,
path: this._params!.path, path: this._params!.path,

View File

@ -0,0 +1,151 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/data-table/ha-data-table";
import type {
DataTableColumnContainer,
DataTableRowData,
SelectionChangedEvent,
} from "../../../../components/data-table/ha-data-table";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-relative-time";
import type { HomeAssistant } from "../../../../types";
@customElement("hui-entity-picker-table")
export class HuiEntityPickerTable extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow?: boolean;
@property({ type: Array }) public entities!: DataTableRowData[];
protected render(): TemplateResult {
return html`
<ha-data-table
auto-height
selectable
.id=${"entity_id"}
.columns=${this._columns(this.narrow!)}
.data=${this.entities}
.dir=${computeRTLDirection(this.hass)}
.searchLabel=${this.hass.localize(
"ui.panel.lovelace.unused_entities.search"
)}
.noDataText=${this.hass.localize(
"ui.panel.lovelace.unused_entities.no_data"
)}
@selection-changed=${this._handleSelectionChanged}
></ha-data-table>
`;
}
private _columns = memoizeOne((narrow: boolean) => {
const columns: DataTableColumnContainer = {
icon: {
title: "",
type: "icon",
template: (_icon, entity: any) => html`
<state-badge
@click=${this._handleEntityClicked}
.hass=${this.hass!}
.stateObj=${entity.stateObj}
></state-badge>
`,
},
name: {
title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity"),
sortable: true,
filterable: true,
grows: true,
direction: "asc",
template: (name, entity: any) => html`
<div @click=${this._handleEntityClicked} style="cursor: pointer;">
${name}
${narrow
? html`
<div class="secondary">
${entity.stateObj.entity_id}
</div>
`
: ""}
</div>
`,
},
};
columns.entity_id = {
title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity_id"),
sortable: true,
filterable: true,
width: "30%",
hidden: narrow,
};
columns.domain = {
title: this.hass!.localize("ui.panel.lovelace.unused_entities.domain"),
sortable: true,
filterable: true,
width: "15%",
hidden: narrow,
};
columns.last_changed = {
title: this.hass!.localize(
"ui.panel.lovelace.unused_entities.last_changed"
),
type: "numeric",
sortable: true,
width: "15%",
hidden: narrow,
template: (lastChanged: string) => html`
<ha-relative-time
.hass=${this.hass!}
.datetime=${lastChanged}
></ha-relative-time>
`,
};
return columns;
});
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
const selectedEntities = ev.detail.value;
fireEvent(this, "selected-changed", { selectedEntities });
}
private _handleEntityClicked(ev: Event) {
const entityId = ((ev.target as HTMLElement).closest(
".mdc-data-table__row"
) as any).rowId;
fireEvent(this, "hass-more-info", {
entityId,
});
}
static get styles(): CSSResult {
return css`
ha-data-table {
--data-table-border-width: 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-entity-picker-table": HuiEntityPickerTable;
}
}

View File

@ -0,0 +1,25 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import { LovelaceConfig } from "../../../../data/lovelace";
export interface CreateCardDialogParams {
lovelaceConfig: LovelaceConfig;
saveConfig: (config: LovelaceConfig) => void;
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 importCreateCardDialog = () =>
import(
/* webpackChunkName: "hui-dialog-create-card" */ "./hui-dialog-create-card"
);
export const showCreateCardDialog = (
element: HTMLElement,
createCardDialogParams: CreateCardDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "hui-dialog-create-card",
dialogImport: importCreateCardDialog,
dialogParams: createCardDialogParams,
});
};

View File

@ -10,39 +10,31 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { mdiPlus } from "@mdi/js";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain"; import { computeDomain } from "../../../../common/entity/compute_domain";
import { computeStateName } from "../../../../common/entity/compute_state_name"; import { computeStateName } from "../../../../common/entity/compute_state_name";
import { import { computeRTL } from "../../../../common/util/compute_rtl";
computeRTL,
computeRTLDirection,
} from "../../../../common/util/compute_rtl";
import "../../../../components/data-table/ha-data-table";
import type {
DataTableColumnContainer,
SelectionChangedEvent,
} from "../../../../components/data-table/ha-data-table";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-icon";
import "../../../../components/ha-relative-time";
import type { LovelaceConfig } from "../../../../data/lovelace";
import type { HomeAssistant } from "../../../../types";
import { computeUnusedEntities } from "../../common/compute-unused-entities"; import { computeUnusedEntities } from "../../common/compute-unused-entities";
import type { Lovelace } from "../../types";
import "../../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
import { showSuggestCardDialog } from "../card-editor/show-suggest-card-dialog"; import { showSuggestCardDialog } from "../card-editor/show-suggest-card-dialog";
import { showSelectViewDialog } from "../select-view/show-select-view-dialog"; import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
import type { DataTableRowData } from "../../../../components/data-table/ha-data-table";
import type { LovelaceConfig } from "../../../../data/lovelace";
import type { HomeAssistant } from "../../../../types";
import type { Lovelace } from "../../types";
import "../card-editor/hui-entity-picker-table";
import "../../../../components/ha-svg-icon";
@customElement("hui-unused-entities") @customElement("hui-unused-entities")
export class HuiUnusedEntities extends LitElement { export class HuiUnusedEntities extends LitElement {
@property({ attribute: false }) public lovelace!: Lovelace; @property({ attribute: false }) public lovelace!: Lovelace;
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow?: boolean; @property({ type: Boolean }) public narrow?: boolean;
@internalProperty() private _unusedEntities: string[] = []; @internalProperty() private _unusedEntities: string[] = [];
@ -52,74 +44,6 @@ export class HuiUnusedEntities extends LitElement {
return this.lovelace.config; return this.lovelace.config;
} }
private _columns = memoizeOne((narrow: boolean) => {
const columns: DataTableColumnContainer = {
icon: {
title: "",
type: "icon",
template: (_icon, entity: any) => html`
<state-badge
@click=${this._handleEntityClicked}
.hass=${this.hass!}
.stateObj=${entity.stateObj}
></state-badge>
`,
},
name: {
title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity"),
sortable: true,
filterable: true,
grows: true,
direction: "asc",
template: (name, entity: any) => html`
<div @click=${this._handleEntityClicked} style="cursor: pointer;">
${name}
${narrow
? html`
<div class="secondary">
${entity.stateObj.entity_id}
</div>
`
: ""}
</div>
`,
},
};
if (narrow) {
return columns;
}
columns.entity_id = {
title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity_id"),
sortable: true,
filterable: true,
width: "30%",
};
columns.domain = {
title: this.hass!.localize("ui.panel.lovelace.unused_entities.domain"),
sortable: true,
filterable: true,
width: "15%",
};
columns.last_changed = {
title: this.hass!.localize(
"ui.panel.lovelace.unused_entities.last_changed"
),
type: "numeric",
sortable: true,
width: "15%",
template: (lastChanged: string) => html`
<ha-relative-time
.hass=${this.hass!}
.datetime=${lastChanged}
></ha-relative-time>
`,
};
return columns;
});
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties); super.updated(changedProperties);
@ -161,9 +85,10 @@ export class HuiUnusedEntities extends LitElement {
</ha-card> </ha-card>
` `
: ""} : ""}
<ha-data-table <hui-entity-picker-table
.columns=${this._columns(this.narrow!)} .hass=${this.hass}
.data=${this._unusedEntities.map((entity) => { .narrow=${this.narrow}
.entities=${this._unusedEntities.map((entity) => {
const stateObj = this.hass!.states[entity]; const stateObj = this.hass!.states[entity];
return { return {
icon: "", icon: "",
@ -173,18 +98,9 @@ export class HuiUnusedEntities extends LitElement {
domain: computeDomain(entity), domain: computeDomain(entity),
last_changed: stateObj!.last_changed, last_changed: stateObj!.last_changed,
}; };
})} }) as DataTableRowData[]}
.id=${"entity_id"} @selected-changed=${this._handleSelectedChanged}
selectable ></hui-entity-picker-table>
@selection-changed=${this._handleSelectionChanged}
.dir=${computeRTLDirection(this.hass)}
.searchLabel=${this.hass.localize(
"ui.panel.lovelace.unused_entities.search"
)}
.noDataText=${this.hass.localize(
"ui.panel.lovelace.unused_entities.no_data"
)}
></ha-data-table>
</div> </div>
<div <div
class="fab ${classMap({ class="fab ${classMap({
@ -211,19 +127,8 @@ export class HuiUnusedEntities extends LitElement {
this._unusedEntities = [...unusedEntities].sort(); this._unusedEntities = [...unusedEntities].sort();
} }
private _handleSelectionChanged( private _handleSelectedChanged(ev: CustomEvent): void {
ev: HASSDomEvent<SelectionChangedEvent> this._selectedEntities = ev.detail.selectedEntities;
): void {
this._selectedEntities = ev.detail.value;
}
private _handleEntityClicked(ev: Event) {
const entityId = ((ev.target as HTMLElement).closest(
".mdc-data-table__row"
) as any).rowId;
fireEvent(this, "hass-more-info", {
entityId,
});
} }
private _addToLovelaceView(): void { private _addToLovelaceView(): void {
@ -258,25 +163,22 @@ export class HuiUnusedEntities extends LitElement {
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* min-height: calc(100vh - 112px); */
height: 100%; height: 100%;
} }
ha-card { ha-card {
--ha-card-box-shadow: none; --ha-card-box-shadow: none;
--ha-card-border-radius: 0; --ha-card-border-radius: 0;
} }
ha-data-table { hui-entity-picker-table {
--data-table-border-width: 0;
flex-grow: 1; flex-grow: 1;
margin-top: -20px; margin-top: -20px;
} }
.fab { .fab {
overflow: hidden; position: sticky;
position: absolute; float: right;
right: 0; right: calc(16px + env(safe-area-inset-right));
bottom: 0; bottom: calc(16px + env(safe-area-inset-bottom));
padding: 16px;
padding-right: calc(16px + env(safe-area-inset-right));
padding-bottom: calc(16px + env(safe-area-inset-bottom));
z-index: 1; z-index: 1;
} }
.fab.rtl { .fab.rtl {

View File

@ -23,11 +23,11 @@ import { computeCardSize } from "../common/compute-card-size";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
import { createBadgeElement } from "../create-element/create-badge-element"; import { createBadgeElement } from "../create-element/create-badge-element";
import { createCardElement } from "../create-element/create-card-element"; import { createCardElement } from "../create-element/create-card-element";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
import { Lovelace, LovelaceBadge, LovelaceCard } from "../types"; import { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js"; import { mdiPlus } from "@mdi/js";
import { nextRender } from "../../../common/util/render-status"; import { nextRender } from "../../../common/util/render-status";
import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
let editCodeLoaded = false; let editCodeLoaded = false;
@ -186,7 +186,7 @@ export class HUIView extends LitElement {
} }
private _addCard(): void { private _addCard(): void {
showEditCardDialog(this, { showCreateCardDialog(this, {
lovelaceConfig: this.lovelace!.config, lovelaceConfig: this.lovelace!.config,
saveConfig: this.lovelace!.saveConfig, saveConfig: this.lovelace!.saveConfig,
path: [this.index!], path: [this.index!],

View File

@ -260,6 +260,7 @@
}, },
"common": { "common": {
"and": "and", "and": "and",
"continue": "Continue",
"previous": "Previous", "previous": "Previous",
"loading": "Loading", "loading": "Loading",
"refresh": "Refresh", "refresh": "Refresh",
@ -2351,7 +2352,11 @@
}, },
"cardpicker": { "cardpicker": {
"no_description": "No description available.", "no_description": "No description available.",
"custom_card": "Custom" "custom_card": "Custom",
"domain": "Domain",
"entity": "Entity",
"by_entity": "By Entity",
"by_card": "By Card"
} }
}, },
"warning": { "warning": {