mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-13 12:26:35 +00:00
Add picture uploader to area (#10544)
This commit is contained in:
parent
6623e5f017
commit
4cb45d6313
@ -340,7 +340,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
item-value-path="area_id"
|
item-value-path="area_id"
|
||||||
item-id-path="area_id"
|
item-id-path="area_id"
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
.value=${this._value}
|
.value=${this.value}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
${comboBoxRenderer(rowRenderer)}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@ -431,12 +431,24 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
this._areas = [...this._areas!, area];
|
this._areas = [...this._areas!, area];
|
||||||
|
(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
|
||||||
|
);
|
||||||
this._setValue(area.area_id);
|
this._setValue(area.area_id);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
text: this.hass.localize(
|
title: this.hass.localize(
|
||||||
"ui.components.area-picker.add_dialog.failed_create_area"
|
"ui.components.area-picker.add_dialog.failed_create_area"
|
||||||
),
|
),
|
||||||
|
text: err.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7,10 +7,12 @@ import { HomeAssistant } from "../types";
|
|||||||
export interface AreaRegistryEntry {
|
export interface AreaRegistryEntry {
|
||||||
area_id: string;
|
area_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
picture?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AreaRegistryEntryMutableParams {
|
export interface AreaRegistryEntryMutableParams {
|
||||||
name: string;
|
name: string;
|
||||||
|
picture?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createAreaRegistryEntry = (
|
export const createAreaRegistryEntry = (
|
||||||
|
@ -66,7 +66,7 @@ export const computeEntityRegistryName = (
|
|||||||
return entry.name;
|
return entry.name;
|
||||||
}
|
}
|
||||||
const state = hass.states[entry.entity_id];
|
const state = hass.states[entry.entity_id];
|
||||||
return state ? computeStateName(state) : null;
|
return state ? computeStateName(state) : entry.entity_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getExtendedEntityRegistryEntry = (
|
export const getExtendedEntityRegistryEntry = (
|
||||||
|
@ -4,7 +4,7 @@ export interface CropOptions {
|
|||||||
round: boolean;
|
round: boolean;
|
||||||
type?: "image/jpeg" | "image/png";
|
type?: "image/jpeg" | "image/png";
|
||||||
quality?: number;
|
quality?: number;
|
||||||
aspectRatio: number;
|
aspectRatio?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HaImageCropperDialogParams {
|
export interface HaImageCropperDialogParams {
|
||||||
|
@ -3,19 +3,31 @@ import "@polymer/paper-input/paper-input";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { navigate } from "../../../common/navigate";
|
|
||||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
|
import "../../../components/ha-alert";
|
||||||
|
import "../../../components/ha-picture-upload";
|
||||||
|
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
||||||
import { AreaRegistryEntryMutableParams } from "../../../data/area_registry";
|
import { AreaRegistryEntryMutableParams } from "../../../data/area_registry";
|
||||||
|
import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
import { haStyleDialog } from "../../../resources/styles";
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";
|
import { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";
|
||||||
|
|
||||||
|
const cropOptions: CropOptions = {
|
||||||
|
round: false,
|
||||||
|
type: "image/jpeg",
|
||||||
|
quality: 0.75,
|
||||||
|
aspectRatio: 1.78,
|
||||||
|
};
|
||||||
|
|
||||||
class DialogAreaDetail extends LitElement {
|
class DialogAreaDetail extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _name!: string;
|
@state() private _name!: string;
|
||||||
|
|
||||||
|
@state() private _picture!: string | null;
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@state() private _params?: AreaRegistryDetailDialogParams;
|
@state() private _params?: AreaRegistryDetailDialogParams;
|
||||||
@ -28,6 +40,7 @@ class DialogAreaDetail extends LitElement {
|
|||||||
this._params = params;
|
this._params = params;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._name = this._params.entry ? this._params.entry.name : "";
|
this._name = this._params.entry ? this._params.entry.name : "";
|
||||||
|
this._picture = this._params.entry?.picture || null;
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +68,9 @@ class DialogAreaDetail extends LitElement {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
${this._error
|
||||||
|
? html` <ha-alert alert-type="error">${this._error}</ha-alert> `
|
||||||
|
: ""}
|
||||||
<div class="form">
|
<div class="form">
|
||||||
${entry
|
${entry
|
||||||
? html`
|
? html`
|
||||||
@ -78,6 +93,13 @@ class DialogAreaDetail extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.invalid=${nameInvalid}
|
.invalid=${nameInvalid}
|
||||||
></paper-input>
|
></paper-input>
|
||||||
|
<ha-picture-upload
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._picture}
|
||||||
|
crop
|
||||||
|
.cropOptions=${cropOptions}
|
||||||
|
@change=${this._pictureChanged}
|
||||||
|
></ha-picture-upload>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${entry
|
${entry
|
||||||
@ -120,18 +142,24 @@ class DialogAreaDetail extends LitElement {
|
|||||||
this._name = ev.detail.value;
|
this._name = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _pictureChanged(ev: PolymerChangedEvent<string | null>) {
|
||||||
|
this._error = undefined;
|
||||||
|
this._picture = (ev.target as HaPictureUpload).value;
|
||||||
|
}
|
||||||
|
|
||||||
private async _updateEntry() {
|
private async _updateEntry() {
|
||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
try {
|
try {
|
||||||
const values: AreaRegistryEntryMutableParams = {
|
const values: AreaRegistryEntryMutableParams = {
|
||||||
name: this._name.trim(),
|
name: this._name.trim(),
|
||||||
|
picture: this._picture,
|
||||||
};
|
};
|
||||||
if (this._params!.entry) {
|
if (this._params!.entry) {
|
||||||
await this._params!.updateEntry!(values);
|
await this._params!.updateEntry!(values);
|
||||||
} else {
|
} else {
|
||||||
await this._params!.createEntry!(values);
|
await this._params!.createEntry!(values);
|
||||||
}
|
}
|
||||||
this._params = undefined;
|
this.closeDialog();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error =
|
this._error =
|
||||||
err.message ||
|
err.message ||
|
||||||
@ -145,13 +173,11 @@ class DialogAreaDetail extends LitElement {
|
|||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
try {
|
try {
|
||||||
if (await this._params!.removeEntry!()) {
|
if (await this._params!.removeEntry!()) {
|
||||||
this._params = undefined;
|
this.closeDialog();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._submitting = false;
|
this._submitting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate("/config/areas/dashboard");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
@ -161,9 +187,6 @@ class DialogAreaDetail extends LitElement {
|
|||||||
.form {
|
.form {
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
}
|
}
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiCog } from "@mdi/js";
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import { mdiImagePlus, mdiPencil } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
|
import { afterNextRender } from "../../../common/util/render-status";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-icon-next";
|
||||||
import {
|
import {
|
||||||
AreaRegistryEntry,
|
AreaRegistryEntry,
|
||||||
deleteAreaRegistryEntry,
|
deleteAreaRegistryEntry,
|
||||||
@ -134,25 +138,59 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
.tabs=${configSections.integrations}
|
.tabs=${configSections.integrations}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
>
|
>
|
||||||
${this.narrow ? html` <span slot="header"> ${area.name} </span> ` : ""}
|
${this.narrow
|
||||||
|
? html`<span slot="header"> ${area.name} </span>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="toolbar-icon"
|
.path=${mdiPencil}
|
||||||
.path=${mdiCog}
|
.entry=${area}
|
||||||
.entry=${area}
|
@click=${this._showSettings}
|
||||||
@click=${this._showSettings}
|
slot="toolbar-icon"
|
||||||
.label=${this.hass.localize("ui.panel.config.areas.edit_settings")}
|
.label=${this.hass.localize(
|
||||||
></ha-icon-button>
|
"ui.panel.config.areas.edit_settings"
|
||||||
|
)}
|
||||||
|
></ha-icon-button>`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
${!this.narrow
|
${!this.narrow
|
||||||
? html`
|
? html`
|
||||||
<div class="fullwidth">
|
<div class="fullwidth">
|
||||||
<h1>${area.name}</h1>
|
<h1>
|
||||||
|
${area.name}
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiPencil}
|
||||||
|
.entry=${area}
|
||||||
|
@click=${this._showSettings}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.areas.edit_settings"
|
||||||
|
)}
|
||||||
|
></ha-icon-button>
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="column">
|
<div class="column">
|
||||||
|
${area.picture
|
||||||
|
? html`<div class="img-container">
|
||||||
|
<img src=${area.picture} /><ha-icon-button
|
||||||
|
.path=${mdiPencil}
|
||||||
|
.entry=${area}
|
||||||
|
@click=${this._showSettings}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.areas.edit_settings"
|
||||||
|
)}
|
||||||
|
class="img-edit-btn"
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>`
|
||||||
|
: html`<mwc-button
|
||||||
|
.entry=${area}
|
||||||
|
@click=${this._showSettings}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.areas.add_picture"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiImagePlus} slot="icon"></ha-svg-icon>
|
||||||
|
</mwc-button>`}
|
||||||
<ha-card
|
<ha-card
|
||||||
.header=${this.hass.localize("ui.panel.config.devices.caption")}
|
.header=${this.hass.localize("ui.panel.config.devices.caption")}
|
||||||
>${devices.length
|
>${devices.length
|
||||||
@ -181,7 +219,8 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
.header=${this.hass.localize(
|
.header=${this.hass.localize(
|
||||||
"ui.panel.config.areas.editor.linked_entities_caption"
|
"ui.panel.config.areas.editor.linked_entities_caption"
|
||||||
)}
|
)}
|
||||||
>${entities.length
|
>
|
||||||
|
${entities.length
|
||||||
? entities.map(
|
? entities.map(
|
||||||
(entity) =>
|
(entity) =>
|
||||||
html`
|
html`
|
||||||
@ -390,6 +429,7 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteAreaRegistryEntry(this.hass!, entry!.area_id);
|
await deleteAreaRegistryEntry(this.hass!, entry!.area_id);
|
||||||
|
afterNextRender(() => history.back());
|
||||||
return true;
|
return true;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return false;
|
return false;
|
||||||
@ -403,7 +443,7 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: 0;
|
margin: 0;
|
||||||
font-family: var(--paper-font-headline_-_font-family);
|
font-family: var(--paper-font-headline_-_font-family);
|
||||||
-webkit-font-smoothing: var(
|
-webkit-font-smoothing: var(
|
||||||
--paper-font-headline_-_-webkit-font-smoothing
|
--paper-font-headline_-_-webkit-font-smoothing
|
||||||
@ -413,6 +453,13 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
letter-spacing: var(--paper-font-headline_-_letter-spacing);
|
letter-spacing: var(--paper-font-headline_-_letter-spacing);
|
||||||
line-height: var(--paper-font-headline_-_line-height);
|
line-height: var(--paper-font-headline_-_line-height);
|
||||||
opacity: var(--dark-primary-opacity);
|
opacity: var(--dark-primary-opacity);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: var(--ha-card-border-radius, 4px);
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@ -458,6 +505,34 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
paper-item.no-link {
|
paper-item.no-link {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-card > a:first-child {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ha-card > *:first-child {
|
||||||
|
margin-top: -16px;
|
||||||
|
}
|
||||||
|
.img-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.img-edit-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.img-container:hover .img-edit-btn {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.img-edit-btn::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--card-background-color);
|
||||||
|
opacity: 0.5;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
|
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
|
||||||
import { navigate } from "../../../common/navigate";
|
|
||||||
import {
|
|
||||||
DataTableColumnContainer,
|
|
||||||
RowClickedEvent,
|
|
||||||
} from "../../../components/data-table/ha-data-table";
|
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
@ -21,7 +14,7 @@ import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
|||||||
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import "../../../layouts/hass-loading-screen";
|
import "../../../layouts/hass-loading-screen";
|
||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../layouts/hass-tabs-subpage";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
@ -53,97 +46,51 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
entities: EntityRegistryEntry[]
|
entities: EntityRegistryEntry[]
|
||||||
) =>
|
) =>
|
||||||
areas.map((area) => {
|
areas.map((area) => {
|
||||||
|
let noDevicesInArea = 0;
|
||||||
|
let noServicesInArea = 0;
|
||||||
|
let noEntitiesInArea = 0;
|
||||||
|
|
||||||
const devicesInArea = new Set();
|
const devicesInArea = new Set();
|
||||||
|
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
if (device.area_id === area.area_id) {
|
if (device.area_id === area.area_id) {
|
||||||
devicesInArea.add(device.id);
|
devicesInArea.add(device.id);
|
||||||
|
if (device.entry_type === "service") {
|
||||||
|
noServicesInArea++;
|
||||||
|
} else {
|
||||||
|
noDevicesInArea++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let entitiesInArea = 0;
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
if (
|
if (
|
||||||
entity.area_id
|
entity.area_id
|
||||||
? entity.area_id === area.area_id
|
? entity.area_id === area.area_id
|
||||||
: devicesInArea.has(entity.device_id)
|
: devicesInArea.has(entity.device_id)
|
||||||
) {
|
) {
|
||||||
entitiesInArea++;
|
noEntitiesInArea++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...area,
|
...area,
|
||||||
devices: devicesInArea.size,
|
devices: noDevicesInArea,
|
||||||
entities: entitiesInArea,
|
services: noServicesInArea,
|
||||||
|
entities: noEntitiesInArea,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
private _columns = memoizeOne(
|
|
||||||
(narrow: boolean): DataTableColumnContainer =>
|
|
||||||
narrow
|
|
||||||
? {
|
|
||||||
name: {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.areas.data_table.area"
|
|
||||||
),
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
grows: true,
|
|
||||||
direction: "asc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.areas.data_table.area"
|
|
||||||
),
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
grows: true,
|
|
||||||
direction: "asc",
|
|
||||||
},
|
|
||||||
devices: {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.areas.data_table.devices"
|
|
||||||
),
|
|
||||||
sortable: true,
|
|
||||||
type: "numeric",
|
|
||||||
width: "20%",
|
|
||||||
direction: "asc",
|
|
||||||
},
|
|
||||||
entities: {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.areas.data_table.entities"
|
|
||||||
),
|
|
||||||
sortable: true,
|
|
||||||
type: "numeric",
|
|
||||||
width: "20%",
|
|
||||||
direction: "asc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.isWide=${this.isWide}
|
.isWide=${this.isWide}
|
||||||
back-path="/config"
|
back-path="/config"
|
||||||
.tabs=${configSections.integrations}
|
.tabs=${configSections.integrations}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.columns=${this._columns(this.narrow)}
|
|
||||||
.data=${this._areas(this.areas, this.devices, this.entities)}
|
|
||||||
@row-click=${this._handleRowClicked}
|
|
||||||
.noDataText=${this.hass.localize(
|
|
||||||
"ui.panel.config.areas.picker.no_areas"
|
|
||||||
)}
|
|
||||||
id="area_id"
|
|
||||||
hasFab
|
|
||||||
clickable
|
|
||||||
>
|
>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="toolbar-icon"
|
slot="toolbar-icon"
|
||||||
@ -151,6 +98,58 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
.path=${mdiHelpCircle}
|
.path=${mdiHelpCircle}
|
||||||
@click=${this._showHelp}
|
@click=${this._showHelp}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
<div class="container">
|
||||||
|
${this._areas(this.areas, this.devices, this.entities).map(
|
||||||
|
(area) =>
|
||||||
|
html`<a href=${`/config/areas/area/${area.area_id}`}
|
||||||
|
><ha-card outlined>
|
||||||
|
<div
|
||||||
|
style=${styleMap({
|
||||||
|
backgroundImage: area.picture
|
||||||
|
? `url(${area.picture})`
|
||||||
|
: undefined,
|
||||||
|
})}
|
||||||
|
class="picture ${!area.picture ? "placeholder" : ""}"
|
||||||
|
></div>
|
||||||
|
<h1 class="card-header">${area.name}</h1>
|
||||||
|
<div class="card-content">
|
||||||
|
<div>
|
||||||
|
${area.devices
|
||||||
|
? html`
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.devices",
|
||||||
|
"count",
|
||||||
|
area.devices
|
||||||
|
)}${area.services ? "," : ""}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${area.services
|
||||||
|
? html`
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.services",
|
||||||
|
"count",
|
||||||
|
area.services
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${(area.devices || area.services) && area.entities
|
||||||
|
? this.hass.localize("ui.common.and")
|
||||||
|
: ""}
|
||||||
|
${area.entities
|
||||||
|
? html`
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.entities",
|
||||||
|
"count",
|
||||||
|
area.entities
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-card></a
|
||||||
|
>`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<ha-fab
|
<ha-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@ -161,7 +160,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
</ha-fab>
|
</ha-fab>
|
||||||
</hass-tabs-subpage-data-table>
|
</hass-tabs-subpage>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,11 +190,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
|
||||||
const areaId = ev.detail.id;
|
|
||||||
navigate(`/config/areas/area/${areaId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _openDialog(entry?: AreaRegistryEntry) {
|
private _openDialog(entry?: AreaRegistryEntry) {
|
||||||
showAreaRegistryDetailDialog(this, {
|
showAreaRegistryDetailDialog(this, {
|
||||||
entry,
|
entry,
|
||||||
@ -210,6 +204,51 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
--app-header-background-color: var(--sidebar-background-color);
|
--app-header-background-color: var(--sidebar-background-color);
|
||||||
--app-header-text-color: var(--sidebar-text-color);
|
--app-header-text-color: var(--sidebar-text-color);
|
||||||
}
|
}
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
grid-gap: 16px 16px;
|
||||||
|
padding: 8px 16px 16px;
|
||||||
|
margin: 0 auto 64px auto;
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
.container > * {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
.picture {
|
||||||
|
height: 150px;
|
||||||
|
width: 100%;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.picture.placeholder::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--sidebar-selected-icon-color);
|
||||||
|
opacity: 0.12;
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
min-height: 16px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-config-areas-dashboard": HaConfigAreasDashboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -376,9 +376,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
result.push({
|
result.push({
|
||||||
...entry,
|
...entry,
|
||||||
entity,
|
entity,
|
||||||
name:
|
name: computeEntityRegistryName(this.hass!, entry),
|
||||||
computeEntityRegistryName(this.hass!, entry) ||
|
|
||||||
this.hass.localize("state.default.unavailable"),
|
|
||||||
unavailable,
|
unavailable,
|
||||||
restored,
|
restored,
|
||||||
area: area ? area.name : undefined,
|
area: area ? area.name : undefined,
|
||||||
|
@ -930,6 +930,7 @@
|
|||||||
"caption": "Areas",
|
"caption": "Areas",
|
||||||
"description": "Group devices and entities into areas",
|
"description": "Group devices and entities into areas",
|
||||||
"edit_settings": "Area settings",
|
"edit_settings": "Area settings",
|
||||||
|
"add_picture": "Add a picture",
|
||||||
"data_table": {
|
"data_table": {
|
||||||
"area": "Area",
|
"area": "Area",
|
||||||
"devices": "Devices",
|
"devices": "Devices",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user