diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts index 22ce4caea1..0d030a5479 100644 --- a/hassio/src/hassio-main.ts +++ b/hassio/src/hassio-main.ts @@ -36,6 +36,7 @@ customElements.get("paper-icon-button").prototype._keyBindings = {}; class HassioMain extends ProvideHassLitMixin(HassRouterPage) { @property() public hass!: HomeAssistant; @property() public panel!: HassioPanelInfo; + @property() public narrow!: boolean; protected routerOptions: RouterOptions = { // Hass.io has a page with tabs, so we route all non-matching routes to it. @@ -108,6 +109,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) { // As long as we have Polymer pages (el as PolymerElement).setProperties({ hass: this.hass, + narrow: this.narrow, supervisorInfo: this._supervisorInfo, hostInfo: this._hostInfo, hassInfo: this._hassInfo, @@ -115,6 +117,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) { }); } else { el.hass = this.hass; + el.narrow = this.narrow; el.supervisorInfo = this._supervisorInfo; el.hostInfo = this._hostInfo; el.hassInfo = this._hassInfo; diff --git a/hassio/src/hassio-pages-with-tabs.ts b/hassio/src/hassio-pages-with-tabs.ts index adb43d373f..018fd2f4d3 100644 --- a/hassio/src/hassio-pages-with-tabs.ts +++ b/hassio/src/hassio-pages-with-tabs.ts @@ -34,6 +34,7 @@ const HAS_REFRESH_BUTTON = ["store", "snapshots"]; @customElement("hassio-pages-with-tabs") class HassioPagesWithTabs extends LitElement { @property() public hass!: HomeAssistant; + @property() public narrow!: boolean; @property() public route!: Route; @property() public supervisorInfo!: HassioSupervisorInfo; @property() public hostInfo!: HassioHostInfo; @@ -45,7 +46,11 @@ class HassioPagesWithTabs extends LitElement { - +
Hass.io
${HAS_REFRESH_BUTTON.includes(page) ? html` diff --git a/src/components/ha-menu-button.ts b/src/components/ha-menu-button.ts index 6c4d55adf7..1930eb608e 100644 --- a/src/components/ha-menu-button.ts +++ b/src/components/ha-menu-button.ts @@ -5,34 +5,113 @@ import { LitElement, html, customElement, + CSSResult, + css, } from "lit-element"; import { fireEvent } from "../common/dom/fire_event"; +import { HomeAssistant } from "../types"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { subscribeNotifications } from "../data/persistent_notification"; +import computeDomain from "../common/entity/compute_domain"; @customElement("ha-menu-button") class HaMenuButton extends LitElement { - @property({ type: Boolean }) - public hassio = false; + @property({ type: Boolean }) public hassio = false; + @property() public narrow!: boolean; + @property() public hass!: HomeAssistant; + @property() private _hasNotifications = false; + private _attachNotifOnConnect = false; + private _unsubNotifications?: UnsubscribeFunc; + + public connectedCallback() { + super.connectedCallback(); + if (this._attachNotifOnConnect) { + this._attachNotifOnConnect = false; + this._subscribeNotifications(); + } + } + + public disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsubNotifications) { + this._attachNotifOnConnect = true; + this._unsubNotifications(); + this._unsubNotifications = undefined; + } + } protected render(): TemplateResult | void { + const hasNotifications = + this.narrow && + (this._hasNotifications || + Object.keys(this.hass.states).some( + (entityId) => computeDomain(entityId) === "configurator" + )); return html` + ${hasNotifications + ? html` +
+ ` + : ""} `; } - // We are not going to use ShadowDOM as we're rendering a single element - // without any CSS used. - protected createRenderRoot(): Element | ShadowRoot { - return this; + protected updated(changedProps) { + super.updated(changedProps); + + if (!changedProps.has("narrow")) { + return; + } + + this.style.visibility = this.narrow ? "initial" : "hidden"; + + if (!this.narrow) { + this._hasNotifications = false; + if (this._unsubNotifications) { + this._unsubNotifications(); + this._unsubNotifications = undefined; + } + return; + } + + this._subscribeNotifications(); + } + + private _subscribeNotifications() { + this._unsubNotifications = subscribeNotifications( + this.hass.connection, + (notifications) => { + this._hasNotifications = notifications.length > 0; + } + ); } private _toggleMenu(): void { fireEvent(this, "hass-toggle-menu"); } + + static get styles(): CSSResult { + return css` + :host { + position: relative; + } + .dot { + position: absolute; + background-color: var(--accent-color); + width: 12px; + height: 12px; + top: 8px; + right: 5px; + border-radius: 50%; + } + `; + } } declare global { diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 6047c8bbcf..341892caf2 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -14,6 +14,7 @@ import "@polymer/paper-listbox/paper-listbox"; import "./ha-icon"; import "../components/user/ha-user-badge"; +import "../components/ha-menu-button"; import { HomeAssistant, PanelInfo } from "../types"; import { fireEvent } from "../common/dom/fire_event"; import { DEFAULT_PANEL } from "../common/const"; @@ -108,6 +109,7 @@ const renderPanel = (hass, panel) => html` */ class HaSidebar extends LitElement { @property() public hass!: HomeAssistant; + @property() public narrow!: boolean; @property({ type: Boolean }) public alwaysExpand = false; @property({ type: Boolean, reflect: true }) public expanded = false; @@ -135,21 +137,18 @@ class HaSidebar extends LitElement { } return html` - ${this.expanded - ? html` - -
Home Assistant
-
- ` - : html` - - `} + { - this.expanded = true; - } - ); + this.addEventListener("mouseenter", () => { + this.expanded = true; + }); this.addEventListener("mouseleave", () => { this._contract(); }); subscribeNotifications(this.hass.connection, (notifications) => { this._notifications = notifications; }); - // Deal with configurator - // private _updateNotifications( - // states: HassEntities, - // persistent: unknown[] - // ): unknown[] { - // const configurator = computeNotifications(states); - // return persistent.concat(configurator); - // } } protected updated(changedProps) { @@ -308,6 +297,10 @@ class HaSidebar extends LitElement { }); } + private _toggleSidebar() { + fireEvent(this, "hass-toggle-menu"); + } + static get styles(): CSSResult { return css` :host { @@ -332,26 +325,35 @@ class HaSidebar extends LitElement { width: 256px; } - .logo { - height: 65px; - box-sizing: border-box; - padding: 8px; + .menu { + height: 64px; + display: flex; + padding: 0 12px; border-bottom: 1px solid transparent; - } - .logo img { - width: 48px; - } - - app-toolbar { white-space: nowrap; font-weight: 400; color: var(--primary-text-color); border-bottom: 1px solid var(--divider-color); background-color: var(--primary-background-color); + font-size: 20px; + align-items: center; + } + :host([expanded]) .menu { + width: 256px; } - app-toolbar a { - color: var(--primary-text-color); + .menu paper-icon-button { + color: var(--sidebar-icon-color); + } + :host([expanded]) .menu paper-icon-button { + margin-right: 23px; + } + + .title { + display: none; + } + :host([expanded]) .title { + display: initial; } paper-listbox { diff --git a/src/layouts/hass-loading-screen.ts b/src/layouts/hass-loading-screen.ts index 28c06a7d16..c2ece403cc 100644 --- a/src/layouts/hass-loading-screen.ts +++ b/src/layouts/hass-loading-screen.ts @@ -12,17 +12,23 @@ import { import "../components/ha-menu-button"; import "../components/ha-paper-icon-button-arrow-prev"; import { haStyle } from "../resources/styles"; +import { HomeAssistant } from "../types"; @customElement("hass-loading-screen") class HassLoadingScreen extends LitElement { @property({ type: Boolean }) public rootnav? = false; + @property() public hass?: HomeAssistant; + @property() public narrow?: boolean; protected render(): TemplateResult | void { return html` ${this.rootnav ? html` - + ` : html` - ${this.root - ? html` - - ` - : html` - - `} +
${this.header}
diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts index 1c27ba844a..d509aa136a 100644 --- a/src/layouts/partial-panel-resolver.ts +++ b/src/layouts/partial-panel-resolver.ts @@ -86,6 +86,8 @@ class PartialPanelResolver extends HassRouterPage { protected createLoadingScreen() { const el = super.createLoadingScreen(); el.rootnav = true; + el.hass = this.hass; + el.narrow = this.narrow; return el; } diff --git a/src/panels/calendar/ha-panel-calendar.js b/src/panels/calendar/ha-panel-calendar.js index 682162b732..a22a502c5e 100644 --- a/src/panels/calendar/ha-panel-calendar.js +++ b/src/panels/calendar/ha-panel-calendar.js @@ -67,7 +67,10 @@ class HaPanelCalendar extends LocalizeMixin(PolymerElement) { - +
[[localize('panel.calendar')]]
diff --git a/src/panels/config/dashboard/ha-config-dashboard.js b/src/panels/config/dashboard/ha-config-dashboard.js index 0e31d22255..edd8f16daf 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.js +++ b/src/panels/config/dashboard/ha-config-dashboard.js @@ -47,7 +47,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) { - +
[[localize('panel.config')]]
@@ -125,6 +125,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) { static get properties() { return { hass: Object, + narrow: Boolean, isWide: Boolean, cloudStatus: Object, showAdvanced: Boolean, diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 2beebc7d06..14a719b93d 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -9,6 +9,7 @@ import { CoreFrontendUserData, getOptimisticFrontendUserDataCollection, } from "../../data/frontend"; +import { PolymerElement } from "@polymer/polymer"; declare global { // for fire event @@ -142,12 +143,29 @@ class HaPanelConfig extends HassRouterPage { } protected updatePageEl(el) { - el.route = this.routeTail; - el.hass = this.hass; - el.showAdvanced = !!(this._coreUserData && this._coreUserData.showAdvanced); - el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide; - el.narrow = this.narrow; - el.cloudStatus = this._cloudStatus; + const showAdvanced = !!( + this._coreUserData && this._coreUserData.showAdvanced + ); + const isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide; + + if ("setProperties" in el) { + // As long as we have Polymer panels + (el as PolymerElement).setProperties({ + route: this.routeTail, + hass: this.hass, + showAdvanced, + isWide, + narrow: this.narrow, + cloudStatus: this._cloudStatus, + }); + } else { + el.route = this.routeTail; + el.hass = this.hass; + el.showAdvanced = showAdvanced; + el.isWide = isWide; + el.narrow = this.narrow; + el.cloudStatus = this._cloudStatus; + } } private async _updateCloudStatus() { diff --git a/src/panels/developer-tools/ha-panel-developer-tools.ts b/src/panels/developer-tools/ha-panel-developer-tools.ts index 09814ff47b..98a1d74e23 100644 --- a/src/panels/developer-tools/ha-panel-developer-tools.ts +++ b/src/panels/developer-tools/ha-panel-developer-tools.ts @@ -37,7 +37,10 @@ class PanelDeveloperTools extends LitElement { - +
Developer Tools
- - - - -
MQTT
-
-
+ + + + - - - - + +
+ -
- -
- - - -
-
- Publish -
-
+
- +
+ Publish +
+ `; } diff --git a/src/panels/history/ha-panel-history.js b/src/panels/history/ha-panel-history.js index fd99ce9ae6..81b16ffac0 100644 --- a/src/panels/history/ha-panel-history.js +++ b/src/panels/history/ha-panel-history.js @@ -65,7 +65,10 @@ class HaPanelHistory extends LocalizeMixin(PolymerElement) { - +
[[localize('panel.history')]]
@@ -116,9 +119,8 @@ class HaPanelHistory extends LocalizeMixin(PolymerElement) { static get properties() { return { - hass: { - type: Object, - }, + hass: Object, + narrow: Boolean, stateHistory: { type: Object, diff --git a/src/panels/iframe/ha-panel-iframe.js b/src/panels/iframe/ha-panel-iframe.js index 8bd544f782..f8de869342 100644 --- a/src/panels/iframe/ha-panel-iframe.js +++ b/src/panels/iframe/ha-panel-iframe.js @@ -16,7 +16,7 @@ class HaPanelIframe extends PolymerElement { } - +
[[panel.title]]
@@ -32,9 +32,9 @@ class HaPanelIframe extends PolymerElement { static get properties() { return { - panel: { - type: Object, - }, + hass: Object, + narrow: Boolean, + panel: Object, }; } } diff --git a/src/panels/logbook/ha-panel-logbook.js b/src/panels/logbook/ha-panel-logbook.js index 78af42fc50..610ded0a68 100644 --- a/src/panels/logbook/ha-panel-logbook.js +++ b/src/panels/logbook/ha-panel-logbook.js @@ -89,7 +89,10 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) { - +
[[localize('panel.logbook')]]
+ `; } diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 014fd58aa6..9c959ade8f 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -131,7 +131,10 @@ class HUIRoot extends LitElement { ` : html` - +
${this.config.title || "Home Assistant"}
- +
[[localize('panel.mailbox')]]
@@ -128,9 +131,8 @@ class HaPanelMailbox extends EventsMixin(LocalizeMixin(PolymerElement)) { static get properties() { return { - hass: { - type: Object, - }, + hass: Object, + narrow: Boolean, platforms: { type: Array, diff --git a/src/panels/map/ha-panel-map.js b/src/panels/map/ha-panel-map.js index 5abcd46892..464a47927e 100644 --- a/src/panels/map/ha-panel-map.js +++ b/src/panels/map/ha-panel-map.js @@ -27,7 +27,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { - +
[[localize('panel.map')]]
@@ -41,6 +41,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { type: Object, observer: "drawEntities", }, + narrow: Boolean, }; } diff --git a/src/panels/profile/ha-panel-profile.js b/src/panels/profile/ha-panel-profile.js index eb55c2fbc0..e8ccba74ae 100644 --- a/src/panels/profile/ha-panel-profile.js +++ b/src/panels/profile/ha-panel-profile.js @@ -55,7 +55,7 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) { - +
[[localize('panel.profile')]]
diff --git a/src/panels/shopping-list/ha-panel-shopping-list.js b/src/panels/shopping-list/ha-panel-shopping-list.js index 174a7e6caf..84aee70125 100644 --- a/src/panels/shopping-list/ha-panel-shopping-list.js +++ b/src/panels/shopping-list/ha-panel-shopping-list.js @@ -67,7 +67,10 @@ class HaPanelShoppingList extends LocalizeMixin(PolymerElement) { - +
[[localize('panel.shopping_list')]]
- +
[[computeTitle(views, defaultView, locationName)]]