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
This commit is contained in:
Paulus Schoutsen 2019-06-27 15:23:05 -07:00 committed by GitHub
parent 5783cdb0d2
commit 2c3cc1fbc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 218 additions and 106 deletions

View File

@ -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",

View File

@ -66,11 +66,28 @@ const computePanels = (hass: HomeAssistant) => {
return result;
};
const renderPanel = (hass, panel) => html`
<a
href="${computeUrl(panel.url_path)}"
data-panel="${panel.url_path}"
tabindex="-1"
>
<paper-icon-item>
<ha-icon slot="item-icon" .icon="${panel.icon}"></ha-icon>
<span class="item-text">
${hass.localize(`panel.${panel.title}`) || panel.title}
</span>
</paper-icon-item>
</a>
`;
/*
* @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`
<app-toolbar>
<div main-title>Home Assistant</div>
${hass.user
? html`
<a href="/profile">
<ha-user-badge .user=${hass.user}></ha-user-badge>
</a>
`
: ""}
</app-toolbar>
${this.expanded
? html`
<app-toolbar>
<div main-title>Home Assistant</div>
</app-toolbar>
`
: html`
<div class="logo">
<img id="logo" src="/static/icons/favicon-192x192.png" />
</div>
`}
<paper-listbox attr-for-selected="data-panel" .selected=${hass.panelUrl}>
<a
@ -106,66 +131,19 @@ class HaSidebar extends LitElement {
</paper-icon-item>
</a>
${computePanels(hass).map(
(panel) => html`
<a
href="${computeUrl(panel.url_path)}"
data-panel="${panel.url_path}"
tabindex="-1"
>
<paper-icon-item>
<ha-icon slot="item-icon" .icon="${panel.icon}"></ha-icon>
<span class="item-text"
>${hass.localize(`panel.${panel.title}`) || panel.title}</span
>
</paper-icon-item>
</a>
`
)}
${this._externalConfig && this._externalConfig.hasSettingsScreen
? html`
<a
aria-label="App Configuration"
href="#external-app-configuration"
tabindex="-1"
@click=${this._handleExternalAppConfiguration}
>
<paper-icon-item>
<ha-icon
slot="item-icon"
icon="hass:cellphone-settings-variant"
></ha-icon>
<span class="item-text"
>${hass.localize(
"ui.sidebar.external_app_configuration"
)}</span
>
</paper-icon-item>
</a>
`
: ""}
${!hass.user
? html`
<paper-icon-item @click=${this._handleLogOut} class="logout">
<ha-icon slot="item-icon" icon="hass:exit-to-app"></ha-icon>
<span class="item-text"
>${hass.localize("ui.sidebar.log_out")}</span
>
</paper-icon-item>
`
: html``}
</paper-listbox>
${panels.map((panel) => renderPanel(hass, panel))}
${hass.user && hass.user.is_admin
? html`
<div>
<div class="divider"></div>
<div class="spacer" disabled></div>
<div class="subheader">
${this.expanded && hass.user && hass.user.is_admin
? html`
<div class="divider" disabled></div>
<div class="subheader" disabled>
${hass.localize("ui.sidebar.developer_tools")}
</div>
<div class="dev-tools">
<div class="dev-tools" disabled>
<a href="/dev-service" tabindex="-1">
<paper-icon-button
icon="hass:remote"
@ -213,14 +191,65 @@ class HaSidebar extends LitElement {
></paper-icon-button>
</a>
</div>
</div>
`
: ""}
<div class="divider" disabled></div>
`
: ""}
${this._externalConfig && this._externalConfig.hasSettingsScreen
? html`
<a
aria-label="App Configuration"
href="#external-app-configuration"
tabindex="-1"
@click=${this._handleExternalAppConfiguration}
>
<paper-icon-item>
<ha-icon
slot="item-icon"
icon="hass:cellphone-settings-variant"
></ha-icon>
<span class="item-text"
>${hass.localize(
"ui.sidebar.external_app_configuration"
)}</span
>
</paper-icon-item>
</a>
`
: ""}
${configPanel ? renderPanel(hass, configPanel) : ""}
${hass.user
? html`
<a href="/profile" data-panel="panel" tabindex="-1">
<paper-icon-item class="profile">
<ha-user-badge
slot="item-icon"
.user=${hass.user}
></ha-user-badge>
<span class="item-text">
${hass.user.name}
</span>
</paper-icon-item>
</a>
`
: html`
<paper-icon-item @click=${this._handleLogOut} class="logout">
<ha-icon slot="item-icon" icon="hass:exit-to-app"></ha-icon>
<span class="item-text"
>${hass.localize("ui.sidebar.log_out")}</span
>
</paper-icon-item>
`}
</paper-listbox>
`;
}
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 {

View File

@ -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`
<div
class="${classMap({
"profile-badge": true,
long: initials.length > 2,
})}"
>
${initials}
</div>
${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%;
}
`;

View File

@ -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`
<iron-media-query
@ -50,7 +53,7 @@ class HomeAssistantMain extends LitElement {
<app-drawer-layout
fullbleed
.forceNarrow=${this._narrow || !hass.dockedSidebar}
.forceNarrow=${this.narrow}
responsive-width="0"
>
<app-drawer
@ -59,13 +62,16 @@ class HomeAssistantMain extends LitElement {
slot="drawer"
.disableSwipe=${disableSwipe}
.swipeOpen=${!disableSwipe}
.persistent=${hass.dockedSidebar}
.persistent=${!this.narrow}
>
<ha-sidebar .hass=${hass}></ha-sidebar>
<ha-sidebar
.hass=${hass}
.alwaysExpand=${this.narrow || hass.dockedSidebar}
></ha-sidebar>
</app-drawer>
<partial-panel-resolver
.narrow=${this._narrow}
.narrow=${this.narrow}
.hass=${hass}
.route=${this.route}
></partial-panel-resolver>
@ -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<boolean>) {
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 {

View File

@ -44,7 +44,7 @@ export const connectionMixin = (
localize: () => "",
translationMetadata,
dockedSidebar: false,
dockedSidebar: true,
moreInfoEntityId: null,
callService: async (domain, service, serviceData = {}) => {
if (__DEV__) {