Convert ha-sidebar to TS/Lit

This commit is contained in:
Paulus Schoutsen 2019-01-27 23:23:07 -08:00
parent 13adee09da
commit 89630a5c7f
4 changed files with 380 additions and 342 deletions

View File

@ -1,341 +0,0 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "./ha-icon";
import "../util/hass-translation";
import LocalizeMixin from "../mixins/localize-mixin";
import isComponentLoaded from "../common/config/is_component_loaded";
/*
* @appliesMixin LocalizeMixin
*/
class HaSidebar extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment iron-positioning">
:host {
--sidebar-text: {
color: var(--sidebar-text-color);
font-weight: 500;
font-size: 14px;
};
height: 100%;
display: block;
overflow: auto;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
border-right: 1px solid var(--divider-color);
background-color: var(--sidebar-background-color, var(--primary-background-color));
}
app-toolbar {
font-weight: 400;
color: var(--primary-text-color);
border-bottom: 1px solid var(--divider-color);
background-color: var(--primary-background-color);
}
app-toolbar a {
color: var(--primary-text-color);
}
paper-listbox {
padding: 0;
}
paper-listbox > a {
@apply --sidebar-text;
text-decoration: none;
--paper-item-icon: {
color: var(--sidebar-icon-color);
};
}
paper-icon-item {
margin: 8px;
padding-left: 9px;
border-radius: 4px;
--paper-item-min-height: 40px;
}
.iron-selected paper-icon-item:before {
border-radius: 4px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
content: "";
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
transition: opacity 15ms linear;
will-change: opacity;
}
.iron-selected paper-icon-item[pressed]:before {
opacity: 0.37;
}
paper-icon-item span {
@apply --sidebar-text;
}
a.iron-selected {
--paper-item-icon: {
color: var(--sidebar-selected-icon-color);
};
}
a.iron-selected .item-text {
color: var(--sidebar-selected-text-color);
}
paper-icon-item.logout {
margin-top: 16px;
}
.divider {
height: 1px;
background-color: var(--divider-color);
margin: 4px 0;
}
.subheader {
@apply --sidebar-text;
padding: 16px;
}
.dev-tools {
padding: 0 8px;
}
.dev-tools a {
color: var(--sidebar-icon-color);
}
.profile-badge {
/* for ripple */
position: relative;
box-sizing: border-box;
width: 40px;
line-height: 40px;
border-radius: 50%;
text-align: center;
background-color: var(--light-primary-color);
text-decoration: none;
color: var(--primary-text-color);
}
.profile-badge.long {
font-size: 80%;
}
</style>
<app-toolbar>
<div main-title=>Home Assistant</div>
<template is='dom-if' if='[[hass.user]]'>
<a href='/profile' class$='[[_computeBadgeClass(_initials)]]'>
<paper-ripple></paper-ripple>
[[_initials]]
</a>
</template>
</app-toolbar>
<paper-listbox attr-for-selected="data-panel" selected="[[hass.panelUrl]]">
<a href='[[_computeUrl(defaultPage)]]' data-panel$="[[defaultPage]]" tabindex="-1">
<paper-icon-item>
<ha-icon slot="item-icon" icon="hass:apps"></ha-icon>
<span class="item-text">[[localize('panel.states')]]</span>
</paper-icon-item>
</a>
<template is="dom-repeat" items="[[panels]]">
<a href='[[_computeUrl(item.url_path)]]' data-panel$='[[item.url_path]]' tabindex="-1">
<paper-icon-item>
<ha-icon slot="item-icon" icon="[[item.icon]]"></ha-icon>
<span class="item-text">[[_computePanelName(localize, item)]]</span>
</paper-icon-item>
</a>
</template>
<template is='dom-if' if='[[!hass.user]]'>
<paper-icon-item on-click='_handleLogOut' class="logout">
<ha-icon slot="item-icon" icon="hass:exit-to-app"></ha-icon>
<span class="item-text">[[localize('ui.sidebar.log_out')]]</span>
</paper-icon-item>
</template>
</paper-listbox>
<div>
<div class="divider"></div>
<div class="subheader">[[localize('ui.sidebar.developer_tools')]]</div>
<div class="dev-tools layout horizontal justified">
<a href="/dev-service" tabindex="-1">
<paper-icon-button
icon="hass:remote"
alt="[[localize('panel.dev-services')]]"
title="[[localize('panel.dev-services')]]"
></paper-icon-button>
</a>
<a href="/dev-state" tabindex="-1">
<paper-icon-button
icon="hass:code-tags"
alt="[[localize('panel.dev-states')]]"
title="[[localize('panel.dev-states')]]"
></paper-icon-button>
</a>
<a href="/dev-event" tabindex="-1">
<paper-icon-button
icon="hass:radio-tower"
alt="[[localize('panel.dev-events')]]"
title="[[localize('panel.dev-events')]]"
></paper-icon-button>
</a>
<a href="/dev-template" tabindex="-1">
<paper-icon-button
icon="hass:file-xml"
alt="[[localize('panel.dev-templates')]]"
title="[[localize('panel.dev-templates')]]"
></paper-icon-button>
</a>
<template is="dom-if" if="[[_mqttLoaded(hass)]]">
<a href="/dev-mqtt" tabindex="-1">
<paper-icon-button
icon="hass:altimeter"
alt="[[localize('panel.dev-mqtt')]]"
title="[[localize('panel.dev-mqtt')]]"
></paper-icon-button>
</a>
</template>
<a href="/dev-info" tabindex="-1">
<paper-icon-button
icon="hass:information-outline"
alt="[[localize('panel.dev-info')]]"
title="[[localize('panel.dev-info')]]"
></paper-icon-button>
</a>
</div>
</div>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
menuShown: {
type: Boolean,
},
menuSelected: {
type: String,
},
narrow: Boolean,
panels: {
type: Array,
computed: "computePanels(hass)",
},
defaultPage: String,
_initials: {
type: String,
computed: "_computeUserInitials(hass.user.name)",
},
};
}
_computeUserInitials(name) {
if (!name) return "user";
return (
name
.trim()
// Split by space and take first 3 words
.split(" ")
.slice(0, 3)
// Of each word, take first letter
.map((s) => s.substr(0, 1))
.join("")
);
}
_computeBadgeClass(initials) {
return `profile-badge ${initials.length > 2 ? "long" : ""}`;
}
_mqttLoaded(hass) {
return isComponentLoaded(hass, "mqtt");
}
_computeUserName(user) {
return user && (user.name || "Unnamed User");
}
_computePanelName(localize, panel) {
return localize(`panel.${panel.title}`) || panel.title;
}
computePanels(hass) {
var panels = hass.panels;
var sortValue = {
map: 1,
logbook: 2,
history: 3,
};
var result = [];
Object.keys(panels).forEach(function(key) {
if (panels[key].title) {
result.push(panels[key]);
}
});
result.sort(function(a, b) {
var aBuiltIn = a.component_name in sortValue;
var bBuiltIn = b.component_name in sortValue;
if (aBuiltIn && bBuiltIn) {
return sortValue[a.component_name] - sortValue[b.component_name];
}
if (aBuiltIn) {
return -1;
}
if (bBuiltIn) {
return 1;
}
// both not built in, sort by title
if (a.title < b.title) {
return -1;
}
if (a.title > b.title) {
return 1;
}
return 0;
});
return result;
}
_computeUrl(urlPath) {
return `/${urlPath}`;
}
_handleLogOut() {
this.fire("hass-logout");
}
}
customElements.define("ha-sidebar", HaSidebar);

View File

@ -0,0 +1,379 @@
import {
LitElement,
html,
CSSResult,
css,
PropertyDeclarations,
PropertyValues,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "./ha-icon";
import isComponentLoaded from "../common/config/is_component_loaded";
import { HomeAssistant, Panel } from "../types";
import { fireEvent } from "../common/dom/fire_event";
const computeInitials = (name: string) => {
if (!name) {
return "user";
}
return (
name
.trim()
// Split by space and take first 3 words
.split(" ")
.slice(0, 3)
// Of each word, take first letter
.map((s) => s.substr(0, 1))
.join("")
);
};
const computeUrl = (urlPath) => `/${urlPath}`;
const computePanels = (hass: HomeAssistant) => {
const panels = hass.panels;
const sortValue = {
map: 1,
logbook: 2,
history: 3,
};
const result: Panel[] = [];
Object.keys(panels).forEach((key) => {
if (panels[key].title) {
result.push(panels[key]);
}
});
result.sort((a, b) => {
const aBuiltIn = a.component_name in sortValue;
const bBuiltIn = b.component_name in sortValue;
if (aBuiltIn && bBuiltIn) {
return sortValue[a.component_name] - sortValue[b.component_name];
}
if (aBuiltIn) {
return -1;
}
if (bBuiltIn) {
return 1;
}
// both not built in, sort by title
if (a.title! < b.title!) {
return -1;
}
if (a.title! > b.title!) {
return 1;
}
return 0;
});
return result;
};
/*
* @appliesMixin LocalizeMixin
*/
class HaSidebar extends LitElement {
public hass?: HomeAssistant;
public defaultPage?: string;
protected render() {
const hass = this.hass;
if (!hass) {
return html``;
}
const initials = hass.user ? computeInitials(hass.user.name) : "";
return html`
<app-toolbar>
<div main-title>Home Assistant</div>
${hass.user
? html`
<a
href="/profile"
class="${classMap({
"profile-badge": true,
long: initials.length > 2,
})}"
>
<paper-ripple></paper-ripple>
${initials}
</a>
`
: ""}
</app-toolbar>
<paper-listbox attr-for-selected="data-panel" .selected=${hass.panelUrl}>
<a
href="${computeUrl(this.defaultPage)}"
data-panel=${this.defaultPage}
tabindex="-1"
>
<paper-icon-item>
<ha-icon slot="item-icon" icon="hass:apps"></ha-icon>
<span class="item-text">${hass.localize("panel.states")}</span>
</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>
`
)}
${!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>
<div>
<div class="divider"></div>
<div class="subheader">
${hass.localize("ui.sidebar.developer_tools")}
</div>
<div class="dev-tools">
<a href="/dev-service" tabindex="-1">
<paper-icon-button
icon="hass:remote"
alt="${hass.localize("panel.dev-services")}"
title="${hass.localize("panel.dev-services")}"
></paper-icon-button>
</a>
<a href="/dev-state" tabindex="-1">
<paper-icon-button
icon="hass:code-tags"
alt="${hass.localize("panel.dev-states")}"
title="${hass.localize("panel.dev-states")}"
></paper-icon-button>
</a>
<a href="/dev-event" tabindex="-1">
<paper-icon-button
icon="hass:radio-tower"
alt="${hass.localize("panel.dev-events")}"
title="${hass.localize("panel.dev-events")}"
></paper-icon-button>
</a>
<a href="/dev-template" tabindex="-1">
<paper-icon-button
icon="hass:file-xml"
alt="${hass.localize("panel.dev-templates")}"
title="${hass.localize("panel.dev-templates")}"
></paper-icon-button>
</a>
${isComponentLoaded(hass, "mqtt")
? html`
<a href="/dev-mqtt" tabindex="-1">
<paper-icon-button
icon="hass:altimeter"
alt="${hass.localize("panel.dev-mqtt")}"
title="${hass.localize("panel.dev-mqtt")}"
></paper-icon-button>
</a>
`
: html``}
<a href="/dev-info" tabindex="-1">
<paper-icon-button
icon="hass:information-outline"
alt="${hass.localize("panel.dev-info")}"
title="${hass.localize("panel.dev-info")}"
></paper-icon-button>
</a>
</div>
</div>
`;
}
static get properties(): PropertyDeclarations {
return {
hass: {},
defaultPage: {},
};
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.hass || !changedProps.has("hass")) {
return false;
}
const oldHass = changedProps.get("hass") as HomeAssistant;
if (!oldHass) {
return true;
}
const hass = this.hass;
return (
hass.panels !== oldHass.panels ||
hass.panelUrl !== oldHass.panelUrl ||
hass.config.components !== oldHass.config.components ||
hass.user !== oldHass.user ||
hass.localize !== oldHass.localize
);
}
private _handleLogOut() {
fireEvent(this, "hass-logout");
}
static get styles(): CSSResult {
return css`
:host {
height: 100%;
display: block;
overflow: auto;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
border-right: 1px solid var(--divider-color);
background-color: var(
--sidebar-background-color,
var(--primary-background-color)
);
}
app-toolbar {
font-weight: 400;
color: var(--primary-text-color);
border-bottom: 1px solid var(--divider-color);
background-color: var(--primary-background-color);
}
app-toolbar a {
color: var(--primary-text-color);
}
paper-listbox {
padding: 0;
}
paper-listbox > a {
color: var(--sidebar-text-color);
font-weight: 500;
font-size: 14px;
text-decoration: none;
}
paper-icon-item {
margin: 8px;
padding-left: 9px;
border-radius: 4px;
--paper-item-min-height: 40px;
}
a ha-icon {
color: var(--sidebar-icon-color);
}
.iron-selected paper-icon-item:before {
border-radius: 4px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
content: "";
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
transition: opacity 15ms linear;
will-change: opacity;
}
.iron-selected paper-icon-item[pressed]:before {
opacity: 0.37;
}
paper-icon-item span {
color: var(--sidebar-text-color);
font-weight: 500;
font-size: 14px;
}
a.iron-selected paper-icon-item ha-icon {
color: var(--sidebar-selected-icon-color);
}
a.iron-selected .item-text {
color: var(--sidebar-selected-text-color);
}
paper-icon-item.logout {
margin-top: 16px;
}
.divider {
height: 1px;
background-color: var(--divider-color);
margin: 4px 0;
}
.subheader {
color: var(--sidebar-text-color);
font-weight: 500;
font-size: 14px;
padding: 16px;
}
.dev-tools {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 8px;
}
.dev-tools a {
color: var(--sidebar-icon-color);
}
.profile-badge {
/* for ripple */
position: relative;
box-sizing: border-box;
width: 40px;
line-height: 40px;
border-radius: 50%;
text-align: center;
background-color: var(--light-primary-color);
text-decoration: none;
color: var(--primary-text-color);
}
.profile-badge.long {
font-size: 80%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-sidebar": HaSidebar;
}
}
customElements.define("ha-sidebar", HaSidebar);

View File

@ -61,7 +61,6 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
persistent="[[dockedSidebar]]"
>
<ha-sidebar
narrow="[[narrow]]"
hass="[[hass]]"
default-page="[[_defaultPage]]"
></ha-sidebar>

View File

@ -4,6 +4,7 @@ export {};
declare global {
// for fire event
interface HASSDomEvents {
"hass-logout": undefined;
"iron-resize": undefined;
"config-refresh": undefined;
"ha-refresh-cloud-status": undefined;