From 2c3cc1fbc7c039f65737c132b9582956f49c44e8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 27 Jun 2019 15:23:05 -0700 Subject: [PATCH] experimental sidebar (#3306) * experimental sidebar * Change default docked sidebar to true * remove delay * Push things down * Speed up animation * Always open on big screens * Move things around * Final tweaks * Lint * Don't open on hover logo --- src/common/entity/domain_icon.ts | 2 + src/components/ha-sidebar.ts | 244 +++++++++++++++++++-------- src/components/user/ha-user-badge.ts | 24 ++- src/layouts/home-assistant-main.ts | 52 +++--- src/state/connection-mixin.ts | 2 +- 5 files changed, 218 insertions(+), 106 deletions(-) diff --git a/src/common/entity/domain_icon.ts b/src/common/entity/domain_icon.ts index 665897b187..783fd1bb5c 100644 --- a/src/common/entity/domain_icon.ts +++ b/src/common/entity/domain_icon.ts @@ -7,6 +7,7 @@ import { DEFAULT_DOMAIN_ICON } from "../const"; const fixedIcons = { alert: "hass:alert", + alexa: "hass:amazon-alexa", automation: "hass:playlist-play", calendar: "hass:calendar", camera: "hass:video", @@ -15,6 +16,7 @@ const fixedIcons = { conversation: "hass:text-to-speech", device_tracker: "hass:account", fan: "hass:fan", + google_assistant: "hass:google-assistant", group: "hass:google-circles-communities", history_graph: "hass:chart-line", homeassistant: "hass:home-assistant", diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 16ef6184cc..b5eb5294d8 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -66,11 +66,28 @@ const computePanels = (hass: HomeAssistant) => { return result; }; +const renderPanel = (hass, panel) => html` + + + + + ${hass.localize(`panel.${panel.title}`) || panel.title} + + + +`; + /* * @appliesMixin LocalizeMixin */ class HaSidebar extends LitElement { @property() public hass?: HomeAssistant; + @property({ type: Boolean }) public alwaysExpand = false; + @property({ type: Boolean, reflect: true }) public expanded = false; @property() public _defaultPage?: string = localStorage.defaultPage || DEFAULT_PANEL; @property() private _externalConfig?: ExternalConfig; @@ -82,17 +99,25 @@ class HaSidebar extends LitElement { return html``; } + const panels = computePanels(hass); + const configPanelIdx = panels.findIndex( + (panel) => panel.component_name === "config" + ); + const configPanel = + configPanelIdx === -1 ? undefined : panels.splice(configPanelIdx, 1)[0]; + return html` - -
Home Assistant
- ${hass.user - ? html` - - - - ` - : ""} -
+ ${this.expanded + ? html` + +
Home Assistant
+
+ ` + : html` + + `} - ${computePanels(hass).map( - (panel) => html` - - - - ${hass.localize(`panel.${panel.title}`) || panel.title} - - - ` - )} - ${this._externalConfig && this._externalConfig.hasSettingsScreen - ? html` - - - - ${hass.localize( - "ui.sidebar.external_app_configuration" - )} - - - ` - : ""} - ${!hass.user - ? html` - - - ${hass.localize("ui.sidebar.log_out")} - - ` - : html``} - + ${panels.map((panel) => renderPanel(hass, panel))} - ${hass.user && hass.user.is_admin - ? html` -
-
+
-
+ ${this.expanded && hass.user && hass.user.is_admin + ? html` +
+ +
${hass.localize("ui.sidebar.developer_tools")}
-
+ -
- ` - : ""} +
+ ` + : ""} + ${this._externalConfig && this._externalConfig.hasSettingsScreen + ? html` + + + + ${hass.localize( + "ui.sidebar.external_app_configuration" + )} + + + ` + : ""} + ${configPanel ? renderPanel(hass, configPanel) : ""} + ${hass.user + ? html` + + + + + + ${hass.user.name} + + + + ` + : html` + + + ${hass.localize("ui.sidebar.log_out")} + + `} + `; } protected shouldUpdate(changedProps: PropertyValues): boolean { - if (changedProps.has("_externalConfig")) { + if ( + changedProps.has("_externalConfig") || + changedProps.has("expanded") || + changedProps.has("alwaysExpand") + ) { return true; } if (!this.hass || !changedProps.has("hass")) { @@ -247,6 +276,26 @@ class HaSidebar extends LitElement { this._externalConfig = conf; }); } + this.shadowRoot!.querySelector("paper-listbox")!.addEventListener( + "mouseenter", + () => { + this.expanded = true; + } + ); + this.addEventListener("mouseleave", () => { + this._contract(); + }); + } + + protected updated(changedProps) { + super.updated(changedProps); + if (changedProps.has("alwaysExpand")) { + this.expanded = this.alwaysExpand; + } + } + + private _contract() { + this.expanded = this.alwaysExpand || false; } private _handleLogOut() { @@ -265,7 +314,7 @@ class HaSidebar extends LitElement { :host { height: 100%; display: block; - overflow: auto; + overflow: hidden auto; -ms-user-select: none; -webkit-user-select: none; -moz-user-select: none; @@ -274,9 +323,27 @@ class HaSidebar extends LitElement { --sidebar-background-color, var(--primary-background-color) ); + width: 64px; + transition: width 0.2s ease-in; + will-change: width; + contain: strict; + } + :host([expanded]) { + width: 256px; + } + + .logo { + height: 65px; + box-sizing: border-box; + padding: 8px; + 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); @@ -288,7 +355,11 @@ class HaSidebar extends LitElement { } paper-listbox { - padding: 0; + padding: 4px 0; + height: calc(100% - 65px); + display: flex; + flex-direction: column; + box-sizing: border-box; } paper-listbox > a { @@ -299,10 +370,15 @@ class HaSidebar extends LitElement { } paper-icon-item { - margin: 8px; - padding-left: 9px; + box-sizing: border-box; + margin: 4px 8px; + padding-left: 12px; border-radius: 4px; --paper-item-min-height: 40px; + width: 48px; + } + :host([expanded]) paper-icon-item { + width: 240px; } ha-icon[slot="item-icon"] { @@ -342,10 +418,29 @@ class HaSidebar extends LitElement { color: var(--sidebar-selected-text-color); } + a .item-text { + display: none; + } + :host([expanded]) a .item-text { + display: block; + } + paper-icon-item.logout { margin-top: 16px; } + paper-icon-item.profile { + padding-left: 4px; + } + .profile .item-text { + margin-left: 8px; + } + + .spacer { + flex: 1; + pointer-events: none; + } + .divider { height: 1px; background-color: var(--divider-color); @@ -357,6 +452,7 @@ class HaSidebar extends LitElement { font-weight: 500; font-size: 14px; padding: 16px; + white-space: nowrap; } .dev-tools { @@ -364,6 +460,8 @@ class HaSidebar extends LitElement { flex-direction: row; justify-content: space-between; padding: 0 8px; + width: 256px; + box-sizing: border-box; } .dev-tools a { diff --git a/src/components/user/ha-user-badge.ts b/src/components/user/ha-user-badge.ts index 8df39f752b..586e6c4b26 100644 --- a/src/components/user/ha-user-badge.ts +++ b/src/components/user/ha-user-badge.ts @@ -7,7 +7,6 @@ import { property, customElement, } from "lit-element"; -import { classMap } from "lit-html/directives/class-map"; import { User } from "../../data/user"; import { CurrentUser } from "../../types"; @@ -33,24 +32,23 @@ class StateBadge extends LitElement { protected render(): TemplateResult | void { const user = this.user; - const initials = user ? computeInitials(user.name) : "?"; - return html` -
2, - })}" - > - ${initials} -
+ ${initials} `; } + protected updated(changedProps) { + super.updated(changedProps); + this.toggleAttribute( + "long", + (this.user ? computeInitials(this.user.name) : "?").length > 2 + ); + } + static get styles(): CSSResult { return css` - .profile-badge { + :host { display: inline-block; box-sizing: border-box; width: 40px; @@ -63,7 +61,7 @@ class StateBadge extends LitElement { overflow: hidden; } - .profile-badge.long { + :host([long]) { font-size: 80%; } `; diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts index 0472f1069a..d2631cd825 100644 --- a/src/layouts/home-assistant-main.ts +++ b/src/layouts/home-assistant-main.ts @@ -18,6 +18,8 @@ import "./partial-panel-resolver"; import { HomeAssistant, Route } from "../types"; import { fireEvent } from "../common/dom/fire_event"; import { PolymerChangedEvent } from "../polymer-types"; +// tslint:disable-next-line: no-duplicate-imports +import { AppDrawerLayoutElement } from "@polymer/app-layout/app-drawer-layout/app-drawer-layout"; const NON_SWIPABLE_PANELS = ["kiosk", "map"]; @@ -29,9 +31,9 @@ declare global { } class HomeAssistantMain extends LitElement { - @property() public hass?: HomeAssistant; + @property() public hass!: HomeAssistant; @property() public route?: Route; - @property() private _narrow?: boolean; + @property({ type: Boolean }) private narrow?: boolean; protected render(): TemplateResult | void { const hass = this.hass; @@ -40,7 +42,8 @@ class HomeAssistantMain extends LitElement { return; } - const disableSwipe = NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1; + const disableSwipe = + !this.narrow || NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1; return html` - + @@ -77,19 +83,17 @@ class HomeAssistantMain extends LitElement { import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar"); this.addEventListener("hass-toggle-menu", () => { - const shouldOpen = !this.drawer.opened; - - if (shouldOpen) { - if (this._narrow) { - this.drawer.open(); + if (this.narrow) { + if (this.drawer.opened) { + this.drawer.close(); } else { - fireEvent(this, "hass-dock-sidebar", { dock: true }); + this.drawer.open(); } } else { - this.drawer.close(); - if (this.hass!.dockedSidebar) { - fireEvent(this, "hass-dock-sidebar", { dock: false }); - } + fireEvent(this, "hass-dock-sidebar", { + dock: !this.hass.dockedSidebar, + }); + setTimeout(() => this.appLayout.resetLayout()); } }); } @@ -97,7 +101,9 @@ class HomeAssistantMain extends LitElement { protected updated(changedProps: PropertyValues) { super.updated(changedProps); - if (changedProps.has("route") && this._narrow) { + this.toggleAttribute("expanded", this.narrow || this.hass.dockedSidebar); + + if (changedProps.has("route") && this.narrow) { this.drawer.close(); } @@ -110,19 +116,27 @@ class HomeAssistantMain extends LitElement { } private _narrowChanged(ev: PolymerChangedEvent) { - this._narrow = ev.detail.value; + this.narrow = ev.detail.value; } private get drawer(): AppDrawerElement { return this.shadowRoot!.querySelector("app-drawer")!; } + private get appLayout(): AppDrawerLayoutElement { + return this.shadowRoot!.querySelector("app-drawer-layout")!; + } + static get styles(): CSSResult { return css` :host { color: var(--primary-text-color); /* remove the grey tap highlights in iOS on the fullscreen touch targets */ -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + --app-drawer-width: 64px; + } + :host([expanded]) { + --app-drawer-width: 256px; } partial-panel-resolver, ha-sidebar { diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index dcf34e1aab..009db79be1 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -44,7 +44,7 @@ export const connectionMixin = ( localize: () => "", translationMetadata, - dockedSidebar: false, + dockedSidebar: true, moreInfoEntityId: null, callService: async (domain, service, serviceData = {}) => { if (__DEV__) {