From ac711e8abcb01f8a2f256ccaf9e2f21b66fc23d0 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Thu, 17 Sep 2020 15:31:47 -0500 Subject: [PATCH] Fisrt commit of complete redesign --- src/components/user/ha-person-badge.ts | 8 +- .../config/dashboard/ha-config-dashboard.ts | 681 ++++++++++++++---- 2 files changed, 554 insertions(+), 135 deletions(-) diff --git a/src/components/user/ha-person-badge.ts b/src/components/user/ha-person-badge.ts index eead0784c2..5683432987 100644 --- a/src/components/user/ha-person-badge.ts +++ b/src/components/user/ha-person-badge.ts @@ -43,16 +43,16 @@ class PersonBadge extends LitElement { display: contents; } .picture { - width: 40px; - height: 40px; + width: var(--person-picture-size, 40px); + height: var(--person-picture-size, 40px); background-size: cover; border-radius: 50%; } .initials { display: inline-block; box-sizing: border-box; - width: 40px; - line-height: 40px; + width: var(--person-picture-size, 40px); + line-height: var(--person-picture-size, 40px); border-radius: 50%; text-align: center; background-color: var(--light-primary-color); diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 2d51fef1de..63b652a22b 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -1,26 +1,49 @@ -import { mdiCloudLock } from "@mdi/js"; +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import { mdiChevronRight } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; +import { HassEntities } from "home-assistant-js-websocket"; import { css, CSSResultArray, customElement, html, + internalProperty, LitElement, property, TemplateResult, } from "lit-element"; -import { classMap } from "lit-html/directives/class-map"; -import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import memoizeOne from "memoize-one"; +import { formatDateTime } from "../../../common/datetime/format_date_time"; +import { computeStateDomain } from "../../../common/entity/compute_state_domain"; +import { + caseInsensitiveCompare, + compare, +} from "../../../common/string/compare"; import "../../../components/ha-card"; import "../../../components/ha-icon-next"; import "../../../components/ha-menu-button"; +import "../../../components/user/ha-person-badge"; +import { AutomationEntity } from "../../../data/automation"; import { CloudStatus } from "../../../data/cloud"; +import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; +import { + DeviceRegistryEntry, + subscribeDeviceRegistry, +} from "../../../data/device_registry"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../data/entity_registry"; +import { domainToName } from "../../../data/integration"; +import { fetchPersons, Person } from "../../../data/person"; import "../../../layouts/ha-app-layout"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import "../ha-config-section"; -import { configSections } from "../ha-panel-config"; +import { HELPER_DOMAINS } from "../helpers/const"; +import { ConfigEntryExtended } from "../integrations/ha-config-integrations"; import "./ha-config-navigation"; @customElement("ha-config-dashboard") @@ -36,100 +59,17 @@ class HaConfigDashboard extends LitElement { @property() public showAdvanced!: boolean; + @internalProperty() private _persons?: Person[]; + + @internalProperty() private _configEntries?: ConfigEntryExtended[]; + + @internalProperty() + private _entityRegistryEntries: EntityRegistryEntry[] = []; + + @internalProperty() + private _deviceRegistryEntries: DeviceRegistryEntry[] = []; + protected render(): TemplateResult { - const content = html` - -
- ${this.hass.localize("ui.panel.config.header")} -
- -
- ${this.hass.localize("ui.panel.config.introduction")} -
- -
- ${this.cloudStatus && isComponentLoaded(this.hass, "cloud") - ? html` - - - - ` - : ""} - ${Object.values(configSections).map( - (section) => html` - - - - ` - )} -
- ${isComponentLoaded(this.hass, "zha") - ? html` -
- ${this.hass.localize( - "ui.panel.config.integration_panel_move.missing_zha", - "integrations_page", - html` - ${this.hass.localize( - "ui.panel.config.integration_panel_move.link_integration_page" - )} - ` - )} -
- ` - : ""} - ${isComponentLoaded(this.hass, "zwave") - ? html` -
- ${this.hass.localize( - "ui.panel.config.integration_panel_move.missing_zwave", - "integrations_page", - html` - ${this.hass.localize( - "ui.panel.config.integration_panel_move.link_integration_page" - )} - ` - )} -
- ` - : ""} - ${!this.showAdvanced - ? html` -
- ${this.hass.localize( - "ui.panel.config.advanced_mode.hint_enable" - )} - ${this.hass.localize( - "ui.panel.config.advanced_mode.link_profile_page" - )}. -
- ` - : ""} -
- `; - - if (!this.narrow && this.hass.dockedSidebar !== "always_hidden") { - return content; - } - return html` @@ -138,65 +78,544 @@ class HaConfigDashboard extends LitElement { .hass=${this.hass} .narrow=${this.narrow} > +
Hey Zack, Welcome Home!
+
+
+ + + ${this._persons?.map( + (person) => html` + + + ${person.name} + ${person.id} + + ` + )} + + + + +
+
Cloud
+
zackbarett@hey.com
+
+ + + Remote UI + Connected + + + + Google Assistant + Enabled + + + + Amazon Alexa + Disabled + + + + Webhooks + 3 active + + + + +
+ + + + Location Settings + Unit system, timezone, etc + + + + Server Control + Stop and Start Home Assistant + + + + Logs + Server Logs + + + + Add-ons + Manage Addons + + + + About + Info about the server + + + + + +
+
+ +
+ ${this._configEntries?.map((entry) => { + const devices = this._getDevices(entry); + const services = this._getServices(entry); + const entities = this._getEntities(entry); - ${content} + return html` +
+
+ +
+

+ ${entry.localized_domain_name} +

+

+ ${entry.localized_domain_name === entry.title + ? "" + : entry.title} +

+ ${devices.length || services.length || entities.length + ? html` +
+ ${devices.length + ? html`${this.hass.localize( + "ui.panel.config.integrations.config_entry.devices", + "count", + devices.length + )}${services.length ? "," : ""}` + : ""} + ${services.length + ? html`${this.hass.localize( + "ui.panel.config.integrations.config_entry.services", + "count", + services.length + )}` + : ""} + ${(devices.length || services.length) && + entities.length + ? this.hass.localize("ui.common.and") + : ""} + ${entities.length + ? html`${this.hass.localize( + "ui.panel.config.integrations.config_entry.entities", + "count", + entities.length + )}` + : ""} +
+ ` + : ""} +
+ `; + })} +
+ +
+ + + ${this._getAutomations(this.hass.states).map( + (automation) => html` + + ${automation.attributes.friendly_name} + ${this.hass.localize( + "ui.card.automation.last_triggered" + )}: + ${automation.attributes.last_triggered + ? formatDateTime( + new Date(automation.attributes.last_triggered), + this.hass.language + ) + : this.hass.localize( + "ui.components.relative_time.never" + )} + + + + ` + )} + + + + + + ${this._getScripts(this.hass.states).map( + (automation) => html` + + ${automation.attributes.friendly_name} + ${this.hass.localize( + "ui.card.automation.last_triggered" + )}: + ${automation.attributes.last_triggered + ? formatDateTime( + new Date(automation.attributes.last_triggered), + this.hass.language + ) + : this.hass.localize( + "ui.components.relative_time.never" + )} + + + + ` + )} + + + + + + ${this._getScenes(this.hass.states).map( + (automation) => html` + + ${automation.attributes.friendly_name} + ${this.hass.localize( + "ui.card.automation.last_triggered" + )}: + ${automation.attributes.last_triggered + ? formatDateTime( + new Date(automation.attributes.last_triggered), + this.hass.language + ) + : this.hass.localize( + "ui.components.relative_time.never" + )} + + + + ` + )} + + + + + + ${this._getHelpers(this.hass.states).map( + (automation) => html` + + ${automation.attributes.friendly_name} + ${this.hass.localize( + "ui.card.automation.last_triggered" + )}: + ${automation.attributes.last_triggered + ? formatDateTime( + new Date(automation.attributes.last_triggered), + this.hass.language + ) + : this.hass.localize( + "ui.components.relative_time.never" + )} + + + + ` + )} + + + +
+
`; } + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this._fetchPersonData(); + this._fetchIntegrationData(); + subscribeEntityRegistry(this.hass.connection, (entries) => { + this._entityRegistryEntries = entries; + }); + subscribeDeviceRegistry(this.hass.connection, (entries) => { + this._deviceRegistryEntries = entries; + }); + } + + private async _fetchPersonData() { + const personData = await fetchPersons(this.hass!); + + const personDataStorage = personData.storage.sort((ent1, ent2) => + compare(ent1.name, ent2.name) + ); + const personDataConfig = personData.config.sort((ent1, ent2) => + compare(ent1.name, ent2.name) + ); + + this._persons = [...personDataStorage, ...personDataConfig]; + } + + private async _fetchIntegrationData() { + getConfigEntries(this.hass).then((configEntries) => { + this._configEntries = configEntries + .slice(0, 6) + .map( + (entry: ConfigEntry): ConfigEntryExtended => ({ + ...entry, + localized_domain_name: domainToName( + this.hass.localize, + entry.domain + ), + }) + ) + .sort((conf1, conf2) => + caseInsensitiveCompare( + conf1.localized_domain_name + conf1.title, + conf2.localized_domain_name + conf2.title + ) + ); + }); + } + + private _getEntities(configEntry: ConfigEntry): EntityRegistryEntry[] { + if (!this._entityRegistryEntries) { + return []; + } + return this._entityRegistryEntries.filter( + (entity) => entity.config_entry_id === configEntry.entry_id + ); + } + + private _getDevices(configEntry: ConfigEntry): DeviceRegistryEntry[] { + if (!this._deviceRegistryEntries) { + return []; + } + return this._deviceRegistryEntries.filter( + (device) => + device.config_entries.includes(configEntry.entry_id) && + device.entry_type !== "service" + ); + } + + private _getServices(configEntry: ConfigEntry): DeviceRegistryEntry[] { + if (!this._deviceRegistryEntries) { + return []; + } + return this._deviceRegistryEntries.filter( + (device) => + device.config_entries.includes(configEntry.entry_id) && + device.entry_type === "service" + ); + } + + private _getAutomations = memoizeOne( + (states: HassEntities): AutomationEntity[] => { + return Object.values(states) + .filter((entity) => computeStateDomain(entity) === "automation") + .slice(0, 2) as AutomationEntity[]; + } + ); + + private _getScripts = memoizeOne( + (states: HassEntities): AutomationEntity[] => { + return Object.values(states) + .filter((entity) => computeStateDomain(entity) === "script") + .slice(0, 2) as AutomationEntity[]; + } + ); + + private _getScenes = memoizeOne( + (states: HassEntities): AutomationEntity[] => { + return Object.values(states) + .filter((entity) => computeStateDomain(entity) === "scene") + .slice(0, 2) as AutomationEntity[]; + } + ); + + private _getHelpers = memoizeOne( + (states: HassEntities): AutomationEntity[] => { + return Object.values(states) + .filter((entity) => HELPER_DOMAINS.includes(computeStateDomain(entity))) + .slice(0, 2) as AutomationEntity[]; + } + ); + static get styles(): CSSResultArray { return [ haStyle, css` + :host { + --mdc-theme-text-secondary-on-background: var(--secondary-text-color); + } + app-header { --app-header-background-color: var(--primary-background-color); } - ha-card:last-child { - margin-bottom: 24px; - } - ha-config-section { - margin-top: -12px; - } - :host([narrow]) ha-config-section { - margin-top: -20px; - } - ha-card { - overflow: hidden; - } - ha-card a { - text-decoration: none; - color: var(--primary-text-color); - } - .promo-advanced { - text-align: center; - color: var(--secondary-text-color); - margin-bottom: 24px; - } - .promo-advanced a { - color: var(--secondary-text-color); - } .content { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + grid-template-rows: 0.8fr; + grid-template-areas: "Left Main Main Main"; + gap: 18px 18px; + padding: 18px; + } + + .main { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-auto-rows: minmax(min-content, max-content); + gap: 18px 18px; + grid-template-areas: + "Integration Integration Integration" + "Integration Integration Integration" + "Automation Script Scene"; + grid-area: Main; + } + + .left { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-auto-rows: minmax(min-content, max-content); + gap: 18px 18px; + grid-template-areas: + "Person Person Person" + "Cloud Cloud Cloud" + "Server Server Server"; + grid-area: Left; + } + + #PersonCard { + --mdc-list-item-graphic-size: 56px; + --person-picture-size: 56px; + grid-area: Person; display: flex; - flex-wrap: wrap; + flex-direction: column; justify-content: space-between; - margin: 0; } - .content > * { - width: calc(50% - 12px); - margin: 12px 0; - align-self: flex-start; + #CloudCard { + grid-area: Cloud; } - .cloud { + #ServerCard { + grid-area: Server; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + #IntegrationCard { + grid-area: Integration; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + .integrations { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr; + gap: 1px 1px; + } + + .card-content { + padding: 16px; + text-align: center; + } + + .image { + display: flex; + align-items: center; + justify-content: center; + height: 60px; + margin-bottom: 16px; + vertical-align: middle; + } + + img { + max-height: 100%; + max-width: 90%; + } + + #AutomationCard { + grid-area: Automation; + } + + #ScriptCard { + grid-area: Script; + } + + #SceneCard { + grid-area: Scene; + } + + .footer { width: 100%; + min-height: 35px; + border-top: 1px solid var(--divider-color); + align-self: flex-end; } - .narrow.content > * { - width: 100%; + .secondary { + font-size: 14px; + line-height: 1.2; } `, ];