Change config panel navigation (#4377)

* Change config panel navigation

* Show active + don't show toolbar?

* Update ha-panel-config.ts

* Change color of menu toolbar

* Update ha-config-router.ts

* Review comments
This commit is contained in:
Bram Kragten 2020-01-12 17:57:38 +01:00 committed by GitHub
parent a8ed87298a
commit 22792c70c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 562 additions and 352 deletions

View File

@ -9,12 +9,14 @@ import {
} from "lit-element"; } from "lit-element";
import "../components/ha-menu-button"; import "../components/ha-menu-button";
import "../components/ha-paper-icon-button-arrow-prev"; import "../components/ha-paper-icon-button-arrow-prev";
import { classMap } from "lit-html/directives/class-map";
@customElement("hass-subpage") @customElement("hass-subpage")
class HassSubpage extends LitElement { class HassSubpage extends LitElement {
@property() @property()
public header?: string; public header?: string;
@property({ type: Boolean })
public showBackButton = true;
@property({ type: Boolean }) @property({ type: Boolean })
public hassio = false; public hassio = false;
@ -25,6 +27,7 @@ class HassSubpage extends LitElement {
aria-label="Back" aria-label="Back"
.hassio=${this.hassio} .hassio=${this.hassio}
@click=${this._backTapped} @click=${this._backTapped}
class=${classMap({ hidden: !this.showBackButton })}
></ha-paper-icon-button-arrow-prev> ></ha-paper-icon-button-arrow-prev>
<div main-title>${this.header}</div> <div main-title>${this.header}</div>
@ -64,6 +67,10 @@ class HassSubpage extends LitElement {
pointer-events: auto; pointer-events: auto;
} }
ha-paper-icon-button-arrow-prev.hidden {
visibility: hidden;
}
[main-title] { [main-title] {
margin: 0 0 0 24px; margin: 0 0 0 24px;
line-height: 20px; line-height: 20px;

View File

@ -5,6 +5,7 @@ import {
css, css,
CSSResult, CSSResult,
property, property,
customElement,
} from "lit-element"; } from "lit-element";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
@ -30,7 +31,8 @@ import { classMap } from "lit-html/directives/class-map";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
class HaConfigAreaRegistry extends LitElement { @customElement("ha-config-areas")
export class HaConfigAreas extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public isWide?: boolean; @property() public isWide?: boolean;
@property() private _areas?: AreaRegistryEntry[]; @property() private _areas?: AreaRegistryEntry[];
@ -51,7 +53,8 @@ class HaConfigAreaRegistry extends LitElement {
} }
return html` return html`
<hass-subpage <hass-subpage
header="${this.hass.localize("ui.panel.config.area_registry.caption")}" .header="${this.hass.localize("ui.panel.config.area_registry.caption")}"
.showBackButton=${!this.isWide}
> >
<ha-config-section .isWide=${this.isWide}> <ha-config-section .isWide=${this.isWide}>
<span slot="header"> <span slot="header">
@ -208,5 +211,3 @@ All devices in this area will become unassigned.`)
`; `;
} }
} }
customElements.define("ha-config-area-registry", HaConfigAreaRegistry);

View File

@ -42,6 +42,7 @@ class HaAutomationPicker extends LitElement {
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<hass-subpage <hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.automation.caption")} .header=${this.hass.localize("ui.panel.config.automation.caption")}
> >
<ha-config-section .isWide=${this.isWide}> <ha-config-section .isWide=${this.isWide}>

View File

@ -63,7 +63,10 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
color: var(--primary-color); color: var(--primary-color);
} }
</style> </style>
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]"> <hass-subpage
showBackButton="[[!isWide]]"
header="[[localize('ui.panel.config.cloud.caption')]]"
>
<div class="content"> <div class="content">
<ha-config-section is-wide="[[isWide]]"> <ha-config-section is-wide="[[isWide]]">
<span slot="header" <span slot="header"

View File

@ -72,7 +72,10 @@ class CloudLogin extends LocalizeMixin(
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
</style> </style>
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]"> <hass-subpage
showBackButton="[[!isWide]]"
header="[[localize('ui.panel.config.cloud.caption')]]"
>
<div class="content"> <div class="content">
<ha-config-section is-wide="[[isWide]]"> <ha-config-section is-wide="[[isWide]]">
<span slot="header" <span slot="header"

View File

@ -33,7 +33,10 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
} }
</style> </style>
<hass-subpage header="[[localize('ui.panel.config.core.caption')]]"> <hass-subpage
header="[[localize('ui.panel.config.core.caption')]]"
showBackButton="[[!isWide]]"
>
<div class$="[[computeClasses(isWide)]]"> <div class$="[[computeClasses(isWide)]]">
<ha-config-section-core <ha-config-section-core
is-wide="[[isWide]]" is-wide="[[isWide]]"

View File

@ -23,12 +23,17 @@ import LocalizeMixin from "../../../mixins/localize-mixin";
class HaConfigCustomize extends LocalizeMixin(PolymerElement) { class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
static get template() { static get template() {
return html` return html`
<style include="ha-style"></style> <style include="ha-style">
ha-paper-icon-button-arrow-prev[hide] {
visibility: hidden;
}
</style>
<app-header-layout has-scrolling-region=""> <app-header-layout has-scrolling-region="">
<app-header slot="header" fixed=""> <app-header slot="header" fixed="">
<app-toolbar> <app-toolbar>
<ha-paper-icon-button-arrow-prev <ha-paper-icon-button-arrow-prev
hide$="[[isWide]]"
on-click="_backTapped" on-click="_backTapped"
></ha-paper-icon-button-arrow-prev> ></ha-paper-icon-button-arrow-prev>
<div main-title=""> <div main-title="">

View File

@ -55,65 +55,67 @@ class HaConfigDashboard extends LitElement {
${isComponentLoaded(this.hass, "cloud") ${isComponentLoaded(this.hass, "cloud")
? html` ? html`
<ha-card> <ha-card>
<a href='/config/cloud' tabindex="-1"> <a href="/config/cloud" tabindex="-1">
<paper-item> <paper-item>
<paper-item-body two-line=""> <paper-item-body two-line="">
${this.hass.localize("ui.panel.config.cloud.caption")} ${this.hass.localize("ui.panel.config.cloud.caption")}
${ ${this.cloudStatus.logged_in
this.cloudStatus.logged_in ? html`
? html` <div secondary="">
<div secondary=""> ${this.hass.localize(
${this.hass.localize( "ui.panel.config.cloud.description_login",
"ui.panel.config.cloud.description_login", "email",
"email", (this.cloudStatus as CloudStatusLoggedIn)
(this.cloudStatus as CloudStatusLoggedIn).email .email
)} )}
</div> </div>
` `
: html` : html`
<div secondary=""> <div secondary="">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.cloud.description_features" "ui.panel.config.cloud.description_features"
)} )}
</div> </div>
` `}
} </paper-item-body>
</paper-item-body> <ha-icon-next></ha-icon-next>
<ha-icon-next></ha-icon-next> </paper-item>
</paper-item> </a>
</ha-card> </ha-card>
</a> `
`
: ""} : ""}
<ha-config-navigation <ha-card>
.hass=${this.hass} <ha-config-navigation
.showAdvanced=${this.showAdvanced} .hass=${this.hass}
.pages=${[ .showAdvanced=${this.showAdvanced}
{ page: "integrations", core: true }, .pages=${[
{ page: "devices", core: true }, { page: "integrations", core: true },
{ page: "automation" }, { page: "devices", core: true },
{ page: "script" }, { page: "automation" },
{ page: "scene" }, { page: "script" },
]} { page: "scene" },
></ha-config-navigation> ]}
></ha-config-navigation>
<ha-config-navigation </ha-card>
.hass=${this.hass} <ha-card>
.showAdvanced=${this.showAdvanced} <ha-config-navigation
.pages=${[ .hass=${this.hass}
{ page: "core", core: true }, .showAdvanced=${this.showAdvanced}
{ page: "server_control", core: true }, .pages=${[
{ page: "entity_registry", core: true }, { page: "core", core: true },
{ page: "area_registry", core: true }, { page: "server_control", core: true },
{ page: "person" }, { page: "entities", core: true },
{ page: "users", core: true }, { page: "areas", core: true },
{ page: "zha" }, { page: "person" },
{ page: "zwave" }, { page: "users", core: true },
{ page: "customize", core: true, advanced: true }, { page: "zha" },
]} { page: "zwave" },
></ha-config-navigation> { page: "customize", core: true, advanced: true },
]}
></ha-config-navigation>
</ha-card>
${!this.showAdvanced ${!this.showAdvanced
? html` ? html`

View File

@ -16,11 +16,13 @@ import {
css, css,
} from "lit-element"; } from "lit-element";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
export interface ConfigPageNavigation { export interface ConfigPageNavigation {
page: string; page: string;
core?: boolean; core?: boolean;
advanced?: boolean; advanced?: boolean;
info?: any;
} }
@customElement("ha-config-navigation") @customElement("ha-config-navigation")
@ -28,31 +30,56 @@ class HaConfigNavigation extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
@property() public pages!: ConfigPageNavigation[]; @property() public pages!: ConfigPageNavigation[];
@property() public curPage!: string;
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<ha-card> <paper-listbox attr-for-selected="data-page" .selected=${this.curPage}>
${this.pages.map(({ page, core, advanced }) => ${this.pages.map(({ page, core, advanced, info }) =>
(core || isComponentLoaded(this.hass, page)) && (core || isComponentLoaded(this.hass, page)) &&
(!advanced || this.showAdvanced) (!advanced || this.showAdvanced)
? html` ? html`
<a href=${`/config/${page}`}> <a
href=${`/config/${page}`}
aria-role="option"
data-page="${page}"
tabindex="-1"
>
<paper-item> <paper-item>
<paper-item-body two-line=""> <paper-item-body two-line>
${this.hass.localize(`ui.panel.config.${page}.caption`)} ${this.hass.localize(`ui.panel.config.${page}.caption`)}
<div secondary> ${page === "cloud" && (info as CloudStatus)
${this.hass.localize( ? info.logged_in
`ui.panel.config.${page}.description` ? html`
)} <div secondary>
</div> ${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(info as CloudStatusLoggedIn).email
)}
</div>
`
: html`
<div secondary>
${this.hass.localize(
"ui.panel.config.cloud.description_features"
)}
</div>
`
: html`
<div secondary>
${this.hass.localize(
`ui.panel.config.${page}.description`
)}
</div>
`}
</paper-item-body> </paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item> </paper-item>
</a> </a>
` `
: "" : ""
)} )}
</ha-card> </paper-listbox>
`; `;
} }
@ -62,6 +89,24 @@ class HaConfigNavigation extends LitElement {
text-decoration: none; text-decoration: none;
color: var(--primary-text-color); color: var(--primary-text-color);
} }
.iron-selected paper-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-item[pressed]:before {
opacity: 0.37;
}
`; `;
} }
} }

View File

@ -20,7 +20,7 @@ import "@polymer/paper-item/paper-item-body";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import "../../../../components/ha-switch"; import "../../../../components/ha-switch";
import { showEntityRegistryDetailDialog } from "../../entity_registry/show-dialog-entity-registry-detail"; import { showEntityRegistryDetailDialog } from "../../entities/show-dialog-entity-registry-detail";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain"; import { computeDomain } from "../../../../common/entity/compute_domain";
import { domainIcon } from "../../../../common/entity/domain_icon"; import { domainIcon } from "../../../../common/entity/domain_icon";

View File

@ -20,6 +20,7 @@ import { AreaRegistryEntry } from "../../../data/area_registry";
export class HaConfigDeviceDashboard extends LitElement { export class HaConfigDeviceDashboard extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public narrow = false; @property() public narrow = false;
@property() public isWide = false;
@property() public devices!: DeviceRegistryEntry[]; @property() public devices!: DeviceRegistryEntry[];
@property() public entries!: ConfigEntry[]; @property() public entries!: ConfigEntry[];
@property() public entities!: EntityRegistryEntry[]; @property() public entities!: EntityRegistryEntry[];
@ -29,7 +30,8 @@ export class HaConfigDeviceDashboard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<hass-subpage <hass-subpage
header=${this.hass.localize("ui.panel.config.devices.caption")} .showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.devices.caption")}
> >
<div class="content"> <div class="content">
<ha-devices-data-table <ha-devices-data-table

View File

@ -28,6 +28,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
class HaConfigDevices extends HassRouterPage { class HaConfigDevices extends HassRouterPage {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
@ -97,6 +98,7 @@ class HaConfigDevices extends HassRouterPage {
pageEl.devices = this._deviceRegistryEntries; pageEl.devices = this._deviceRegistryEntries;
pageEl.areas = this._areas; pageEl.areas = this._areas;
pageEl.narrow = this.narrow; pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced; pageEl.showAdvanced = this.showAdvanced;
} }

View File

@ -6,6 +6,7 @@ import {
CSSResult, CSSResult,
property, property,
query, query,
customElement,
} from "lit-element"; } from "lit-element";
import { styleMap } from "lit-html/directives/style-map"; import { styleMap } from "lit-html/directives/style-map";
@ -46,7 +47,8 @@ import {
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation"; import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
class HaConfigEntityRegistry extends LitElement { @customElement("ha-config-entities")
export class HaConfigEntities extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public isWide!: boolean; @property() public isWide!: boolean;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@ -211,9 +213,10 @@ class HaConfigEntityRegistry extends LitElement {
} }
return html` return html`
<hass-subpage <hass-subpage
header="${this.hass.localize( .header="${this.hass.localize(
"ui.panel.config.entity_registry.caption" "ui.panel.config.entity_registry.caption"
)}" )}"
.showBackButton=${!this.isWide}
> >
<div class="content"> <div class="content">
<div class="intro"> <div class="intro">
@ -496,14 +499,14 @@ class HaConfigEntityRegistry extends LitElement {
} }
h2 { h2 {
margin-top: 0; margin-top: 0;
font-family: var(--paper-font-display1_-_font-family); font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var( -webkit-font-smoothing: var(
--paper-font-display1_-_-webkit-font-smoothing --paper-font-headline_-_-webkit-font-smoothing
); );
font-size: var(--paper-font-display1_-_font-size); font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-display1_-_font-weight); font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-display1_-_letter-spacing); letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-display1_-_line-height); line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity); opacity: var(--dark-primary-opacity);
} }
p { p {
@ -511,10 +514,8 @@ class HaConfigEntityRegistry extends LitElement {
-webkit-font-smoothing: var( -webkit-font-smoothing: var(
--paper-font-subhead_-_-webkit-font-smoothing --paper-font-subhead_-_-webkit-font-smoothing
); );
font-size: var(--paper-font-subhead_-_font-size);
font-weight: var(--paper-font-subhead_-_font-weight); font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height); line-height: var(--paper-font-subhead_-_line-height);
opacity: var(--dark-primary-opacity);
} }
.intro { .intro {
padding: 24px 16px; padding: 24px 16px;
@ -549,5 +550,3 @@ class HaConfigEntityRegistry extends LitElement {
`; `;
} }
} }
customElements.define("ha-config-entity-registry", HaConfigEntityRegistry);

View File

@ -0,0 +1,170 @@
import { property, customElement } from "lit-element";
import "../../layouts/hass-loading-screen";
import { HomeAssistant } from "../../types";
import { CloudStatus } from "../../data/cloud";
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
import { PolymerElement } from "@polymer/polymer";
declare global {
// for fire event
interface HASSDomEvents {
"ha-refresh-cloud-status": undefined;
}
}
@customElement("ha-config-router")
class HaConfigRouter extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public wideSidebar: boolean = false;
@property() public wide: boolean = false;
@property() public isWide: boolean = false;
@property() public showAdvanced: boolean = false;
@property() public cloudStatus?: CloudStatus;
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
cacheAll: true,
preloadAll: true,
routes: {
areas: {
tag: "ha-config-areas",
load: () =>
import(
/* webpackChunkName: "panel-config-areas" */ "./areas/ha-config-areas"
),
},
automation: {
tag: "ha-config-automation",
load: () =>
import(
/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"
),
},
cloud: {
tag: "ha-config-cloud",
load: () =>
import(
/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"
),
},
core: {
tag: "ha-config-core",
load: () =>
import(
/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"
),
},
devices: {
tag: "ha-config-devices",
load: () =>
import(
/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"
),
},
server_control: {
tag: "ha-config-server-control",
load: () =>
import(
/* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control"
),
},
customize: {
tag: "ha-config-customize",
load: () =>
import(
/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"
),
},
dashboard: {
tag: "ha-config-dashboard",
load: () =>
import(
/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"
),
},
entities: {
tag: "ha-config-entities",
load: () =>
import(
/* webpackChunkName: "panel-config-entities" */ "./entities/ha-config-entities"
),
},
integrations: {
tag: "ha-config-integrations",
load: () =>
import(
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
),
},
person: {
tag: "ha-config-person",
load: () =>
import(
/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"
),
},
script: {
tag: "ha-config-script",
load: () =>
import(
/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"
),
},
scene: {
tag: "ha-config-scene",
load: () =>
import(
/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"
),
},
users: {
tag: "ha-config-users",
load: () =>
import(
/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"
),
},
zha: {
tag: "zha-config-dashboard-router",
load: () =>
import(
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-dashboard-router"
),
},
zwave: {
tag: "ha-config-zwave",
load: () =>
import(
/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"
),
},
},
};
protected updatePageEl(el) {
if ("setProperties" in el) {
// As long as we have Polymer panels
(el as PolymerElement).setProperties({
route: this.routeTail,
hass: this.hass,
showAdvanced: this.showAdvanced,
isWide: this.isWide,
narrow: this.narrow,
cloudStatus: this.cloudStatus,
});
} else {
el.route = this.routeTail;
el.hass = this.hass;
el.showAdvanced = this.showAdvanced;
el.isWide = this.isWide;
el.narrow = this.narrow;
el.cloudStatus = this.cloudStatus;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-router": HaConfigRouter;
}
}

View File

@ -1,98 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../resources/ha-style";
class HaConfigSection extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
padding: 28px 20px 0;
max-width: 1040px;
margin: 0 auto;
}
.header {
@apply --paper-font-display1;
opacity: var(--dark-primary-opacity);
}
.together {
margin-top: 32px;
}
.intro {
@apply --paper-font-subhead;
width: 100%;
max-width: 400px;
margin-right: 40px;
opacity: var(--dark-primary-opacity);
}
.panel {
margin-top: -24px;
}
.panel ::slotted(*) {
margin-top: 24px;
display: block;
}
.narrow.content {
max-width: 640px;
}
.narrow .together {
margin-top: 20px;
}
.narrow .header {
@apply --paper-font-headline;
}
.narrow .intro {
font-size: 14px;
padding-bottom: 20px;
margin-right: 0;
max-width: 500px;
}
</style>
<div class$="[[computeContentClasses(isWide)]]">
<div class="header"><slot name="header"></slot></div>
<div class$="[[computeClasses(isWide)]]">
<div class="intro"><slot name="introduction"></slot></div>
<div class="panel flex-auto"><slot></slot></div>
</div>
</div>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
narrow: {
type: Boolean,
},
isWide: {
type: Boolean,
value: false,
},
};
}
computeContentClasses(isWide) {
var classes = "content ";
return isWide ? classes : classes + "narrow";
}
computeClasses(isWide) {
var classes = "together layout ";
return classes + (isWide ? "horizontal" : "vertical narrow");
}
}
customElements.define("ha-config-section", HaConfigSection);

View File

@ -0,0 +1,65 @@
import { customElement } from "lit-element";
@customElement("ha-config-section")
export class HaConfigSection extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot!.innerHTML = `
<style>
.content {
padding: 28px 20px 0;
max-width: 640px;
margin: 0 auto;
}
.header {
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
.together {
margin-top: 20px;
}
.intro {
font-family: var(--paper-font-subhead_-_font-family);
-webkit-font-smoothing: var(
--paper-font-subhead_-_-webkit-font-smoothing
);
font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height);
width: 100%;
width: 100%;
max-width: 500px;
opacity: var(--dark-primary-opacity);
font-size: 14px;
padding-bottom: 20px;
}
.panel {
margin-top: -24px;
}
.panel ::slotted(*) {
margin-top: 24px;
display: block;
}
</style>
<div class="content">
<div class="header"><slot name="header"></slot></div>
<div class="together">
<div class="intro"><slot name="introduction"></slot></div>
<div class="panel"><slot></slot></div>
</div>
</div>
`;
}
}

View File

@ -1,15 +1,26 @@
import { property, PropertyValues, customElement } from "lit-element"; import {
property,
PropertyValues,
customElement,
LitElement,
html,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-item/paper-item";
import "../../layouts/hass-loading-screen"; import "../../layouts/hass-loading-screen";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { HomeAssistant } from "../../types"; import { HomeAssistant, Route } from "../../types";
import { CloudStatus, fetchCloudStatus } from "../../data/cloud"; import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
import { listenMediaQuery } from "../../common/dom/media_query"; import { listenMediaQuery } from "../../common/dom/media_query";
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
import { import {
CoreFrontendUserData,
getOptimisticFrontendUserDataCollection, getOptimisticFrontendUserDataCollection,
CoreFrontendUserData,
} from "../../data/frontend"; } from "../../data/frontend";
import { PolymerElement } from "@polymer/polymer"; import "./ha-config-router";
import "./dashboard/ha-config-navigation";
import { classMap } from "lit-html/directives/class-map";
declare global { declare global {
// for fire event // for fire event
@ -19,133 +30,15 @@ declare global {
} }
@customElement("ha-panel-config") @customElement("ha-panel-config")
class HaPanelConfig extends HassRouterPage { class HaPanelConfig extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@property() public route!: Route;
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
cacheAll: true,
preloadAll: true,
routes: {
area_registry: {
tag: "ha-config-area-registry",
load: () =>
import(
/* webpackChunkName: "panel-config-area-registry" */ "./area_registry/ha-config-area-registry"
),
},
automation: {
tag: "ha-config-automation",
load: () =>
import(
/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"
),
},
cloud: {
tag: "ha-config-cloud",
load: () =>
import(
/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"
),
},
core: {
tag: "ha-config-core",
load: () =>
import(
/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"
),
},
devices: {
tag: "ha-config-devices",
load: () =>
import(
/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"
),
},
server_control: {
tag: "ha-config-server-control",
load: () =>
import(
/* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control"
),
},
customize: {
tag: "ha-config-customize",
load: () =>
import(
/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"
),
},
dashboard: {
tag: "ha-config-dashboard",
load: () =>
import(
/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"
),
},
entity_registry: {
tag: "ha-config-entity-registry",
load: () =>
import(
/* webpackChunkName: "panel-config-entity-registry" */ "./entity_registry/ha-config-entity-registry"
),
},
integrations: {
tag: "ha-config-integrations",
load: () =>
import(
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
),
},
person: {
tag: "ha-config-person",
load: () =>
import(
/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"
),
},
script: {
tag: "ha-config-script",
load: () =>
import(
/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"
),
},
scene: {
tag: "ha-config-scene",
load: () =>
import(
/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"
),
},
users: {
tag: "ha-config-users",
load: () =>
import(
/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"
),
},
zha: {
tag: "zha-config-dashboard-router",
load: () =>
import(
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-dashboard-router"
),
},
zwave: {
tag: "ha-config-zwave",
load: () =>
import(
/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"
),
},
},
};
@property() private _wideSidebar: boolean = false; @property() private _wideSidebar: boolean = false;
@property() private _wide: boolean = false; @property() private _wide: boolean = false;
@property() private _coreUserData?: CoreFrontendUserData; @property() private _coreUserData?: CoreFrontendUserData;
@property() private _showAdvanced = false;
@property() private _cloudStatus?: CloudStatus; @property() private _cloudStatus?: CloudStatus;
private _listeners: Array<() => void> = []; private _listeners: Array<() => void> = [];
@ -168,6 +61,9 @@ class HaPanelConfig extends HassRouterPage {
"core" "core"
).subscribe((coreUserData) => { ).subscribe((coreUserData) => {
this._coreUserData = coreUserData || {}; this._coreUserData = coreUserData || {};
this._showAdvanced = !!(
this._coreUserData && this._coreUserData.showAdvanced
);
}) })
); );
} }
@ -189,31 +85,60 @@ class HaPanelConfig extends HassRouterPage {
); );
} }
protected updatePageEl(el) { protected render() {
const showAdvanced = !!( const dividerPos = this.route.path.indexOf("/", 1);
this._coreUserData && this._coreUserData.showAdvanced const curPage =
); dividerPos === -1
? this.route.path.substr(1)
: this.route.path.substr(1, dividerPos - 1);
const isWide = const isWide =
this.hass.dockedSidebar === "docked" ? this._wideSidebar : this._wide; this.hass.dockedSidebar === "docked" ? this._wideSidebar : this._wide;
if ("setProperties" in el) { return html`
// As long as we have Polymer panels ${isWide
(el as PolymerElement).setProperties({ ? html`
route: this.routeTail, <div class="side-bar">
hass: this.hass, <div class="toolbar">Configuration</div>
showAdvanced, <div class="navigation">
isWide, <ha-config-navigation
narrow: this.narrow, .hass=${this.hass}
cloudStatus: this._cloudStatus, .showAdvanced=${this._showAdvanced}
}); .curPage=${curPage}
} else { .pages=${[
el.route = this.routeTail; { page: "cloud", info: this._cloudStatus },
el.hass = this.hass; { page: "integrations", core: true },
el.showAdvanced = showAdvanced; { page: "devices", core: true },
el.isWide = isWide; { page: "automation" },
el.narrow = this.narrow; { page: "script" },
el.cloudStatus = this._cloudStatus; { page: "scene" },
} { page: "core", core: true },
{ page: "server_control", core: true },
{ page: "entities", core: true },
{ page: "areas", core: true },
{ page: "person" },
{ page: "users", core: true },
{ page: "zha" },
{ page: "zwave" },
{ page: "customize", core: true, advanced: true },
]}
></ha-config-navigation>
</div>
</div>
`
: ""}
<ha-config-router
.hass=${this.hass}
.route=${this.route}
.narrow=${this.narrow}
.isWide=${isWide}
.wide=${this._wide}
.wideSidebar=${this._wideSidebar}
.showAdvanced=${this._showAdvanced}
.cloudStatus=${this._cloudStatus}
class=${classMap({ "wide-config": isWide })}
></ha-config-router>
`;
} }
private async _updateCloudStatus() { private async _updateCloudStatus() {
@ -223,6 +148,54 @@ class HaPanelConfig extends HassRouterPage {
setTimeout(() => this._updateCloudStatus(), 5000); setTimeout(() => this._updateCloudStatus(), 5000);
} }
} }
static get styles(): CSSResult {
return css`
:host {
display: block;
height: 100%;
background-color: var(--primary-background-color);
}
a {
text-decoration: none;
color: var(--primary-text-color);
}
.side-bar {
border-right: 1px solid #e0e0e0;
background: white;
width: 320px;
float: left;
box-sizing: border-box;
position: fixed;
}
.toolbar {
display: flex;
align-items: center;
font-size: 20px;
height: 64px;
padding: 0 16px 0 16px;
pointer-events: none;
background-color: var(--primary-background-color);
font-weight: 400;
color: var(--primary-text-color);
border-bottom: 1px solid var(--divider-color);
}
.wide-config {
float: right;
width: calc(100% - 320px);
height: 100%;
}
.navigation {
height: calc(100vh - 64px);
overflow: auto;
}
`;
}
} }
declare global { declare global {

View File

@ -49,6 +49,7 @@ import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialo
export class HaConfigManagerDashboard extends LitElement { export class HaConfigManagerDashboard extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
@property() public isWide!: boolean;
@property() private configEntries!: ConfigEntry[]; @property() private configEntries!: ConfigEntry[];
@ -72,7 +73,8 @@ export class HaConfigManagerDashboard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<hass-subpage <hass-subpage
header=${this.hass.localize("ui.panel.config.integrations.caption")} .showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.integrations.caption")}
> >
<paper-menu-button <paper-menu-button
close-on-activate close-on-activate

View File

@ -39,6 +39,7 @@ declare global {
class HaConfigIntegrations extends HassRouterPage { class HaConfigIntegrations extends HassRouterPage {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
@ -101,6 +102,7 @@ class HaConfigIntegrations extends HassRouterPage {
pageEl.entityRegistryEntries = this._entityRegistryEntries; pageEl.entityRegistryEntries = this._entityRegistryEntries;
pageEl.configEntries = this._configEntries; pageEl.configEntries = this._configEntries;
pageEl.narrow = this.narrow; pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced; pageEl.showAdvanced = this.showAdvanced;
if (this._currentPage === "dashboard") { if (this._currentPage === "dashboard") {

View File

@ -48,7 +48,10 @@ class HaConfigPerson extends LitElement {
} }
const hass = this.hass; const hass = this.hass;
return html` return html`
<hass-subpage header=${hass.localize("ui.panel.config.person.caption")}> <hass-subpage
.header=${hass.localize("ui.panel.config.person.caption")}
.showBackButton=${!this.isWide}
>
<ha-config-section .isWide=${this.isWide}> <ha-config-section .isWide=${this.isWide}>
<span slot="header" <span slot="header"
>${hass.localize("ui.panel.config.person.caption")}</span >${hass.localize("ui.panel.config.person.caption")}</span

View File

@ -20,6 +20,7 @@ import { HassEntities } from "home-assistant-js-websocket";
class HaConfigScene extends HassRouterPage { class HaConfigScene extends HassRouterPage {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
@property() public scenes: SceneEntity[] = []; @property() public scenes: SceneEntity[] = [];
@ -52,6 +53,7 @@ class HaConfigScene extends HassRouterPage {
protected updatePageEl(pageEl, changedProps: PropertyValues) { protected updatePageEl(pageEl, changedProps: PropertyValues) {
pageEl.hass = this.hass; pageEl.hass = this.hass;
pageEl.narrow = this.narrow; pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced; pageEl.showAdvanced = this.showAdvanced;
if (this.hass) { if (this.hass) {

View File

@ -30,14 +30,16 @@ import { forwardHaptic } from "../../../data/haptics";
class HaSceneDashboard extends LitElement { class HaSceneDashboard extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public scenes!: SceneEntity[]; @property() public scenes!: SceneEntity[];
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<hass-subpage <hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.scene.caption")} .header=${this.hass.localize("ui.panel.config.scene.caption")}
> >
<ha-config-section .isWide=${!this.narrow}> <ha-config-section .isWide=${this.isWide}>
<div slot="header"> <div slot="header">
${this.hass.localize("ui.panel.config.scene.picker.header")} ${this.hass.localize("ui.panel.config.scene.picker.header")}
</div> </div>

View File

@ -69,7 +69,7 @@ interface DeviceEntitiesLookup {
@customElement("ha-scene-editor") @customElement("ha-scene-editor")
export class HaSceneEditor extends SubscribeMixin(LitElement) { export class HaSceneEditor extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public narrow?: boolean; @property() public isWide?: boolean;
@property() public scene?: SceneEntity; @property() public scene?: SceneEntity;
@property() public creatingNew?: boolean; @property() public creatingNew?: boolean;
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
@ -196,7 +196,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
rtl: computeRTL(this.hass), rtl: computeRTL(this.hass),
})}" })}"
> >
<ha-config-section .isWide=${!this.narrow}> <ha-config-section .isWide=${this.isWide}>
<div slot="header"> <div slot="header">
${this.scene ${this.scene
? computeStateName(this.scene) ? computeStateName(this.scene)
@ -222,7 +222,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
</ha-card> </ha-card>
</ha-config-section> </ha-config-section>
<ha-config-section .isWide=${!this.narrow}> <ha-config-section .isWide=${this.isWide}>
<div slot="header"> <div slot="header">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.scene.editor.devices.header" "ui.panel.config.scene.editor.devices.header"
@ -293,7 +293,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
${this.showAdvanced ${this.showAdvanced
? html` ? html`
<ha-config-section .isWide=${!this.narrow}> <ha-config-section .isWide=${this.isWide}>
<div slot="header"> <div slot="header">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.scene.editor.entities.header" "ui.panel.config.scene.editor.entities.header"
@ -371,7 +371,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
</div> </div>
<ha-fab <ha-fab
slot="fab" slot="fab"
?is-wide="${!this.narrow}" ?is-wide="${this.isWide}"
?dirty="${this._dirty}" ?dirty="${this._dirty}"
icon="hass:content-save" icon="hass:content-save"
.title="${this.hass.localize("ui.panel.config.scene.editor.save")}" .title="${this.hass.localize("ui.panel.config.scene.editor.save")}"

View File

@ -35,6 +35,7 @@ class HaScriptPicker extends LitElement {
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<hass-subpage <hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.script.caption")} .header=${this.hass.localize("ui.panel.config.script.caption")}
> >
<ha-config-section .isWide=${this.isWide}> <ha-config-section .isWide=${this.isWide}>
@ -77,7 +78,7 @@ class HaScriptPicker extends LitElement {
)}" )}"
@click=${this._runScript} @click=${this._runScript}
></paper-icon-button> ></paper-icon-button>
<paper-item-body> <paper-item-body two-line>
<div>${computeStateName(script)}</div> <div>${computeStateName(script)}</div>
</paper-item-body> </paper-item-body>
<div class="actions"> <div class="actions">

View File

@ -35,6 +35,7 @@ class HaConfigServerControl extends LocalizeMixin(PolymerElement) {
<hass-subpage <hass-subpage
header="[[localize('ui.panel.config.server_control.caption')]]" header="[[localize('ui.panel.config.server_control.caption')]]"
showBackButton="[[!isWide]]"
> >
<div class$="[[computeClasses(isWide)]]"> <div class$="[[computeClasses(isWide)]]">
<ha-config-section-server-control <ha-config-section-server-control

View File

@ -58,7 +58,10 @@ class HaUserPicker extends EventsMixin(
} }
</style> </style>
<hass-subpage header="[[localize('ui.panel.config.users.picker.title')]]"> <hass-subpage
header="[[localize('ui.panel.config.users.picker.title')]]"
showBackButton="[[!isWide]]"
>
<ha-card> <ha-card>
<template is="dom-repeat" items="[[users]]" as="user"> <template is="dom-repeat" items="[[users]]" as="user">
<a href="[[_computeUrl(user)]]"> <a href="[[_computeUrl(user)]]">
@ -94,7 +97,7 @@ class HaUserPicker extends EventsMixin(
return { return {
hass: Object, hass: Object,
users: Array, users: Array,
isWide: Boolean,
rtl: { rtl: {
type: Boolean, type: Boolean,
reflectToAttribute: true, reflectToAttribute: true,

View File

@ -27,6 +27,7 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
<ha-config-user-picker <ha-config-user-picker
hass="[[hass]]" hass="[[hass]]"
users="[[_users]]" users="[[_users]]"
is-wide="[[isWide]]"
></ha-config-user-picker> ></ha-config-user-picker>
</template> </template>
<template <template
@ -45,6 +46,7 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
static get properties() { static get properties() {
return { return {
hass: Object, hass: Object,
isWide: Boolean,
route: { route: {
type: Object, type: Object,
observer: "_checkRoute", observer: "_checkRoute",

View File

@ -103,7 +103,10 @@ class ZHAConfigDashboard extends LitElement {
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<hass-subpage .header=${this.hass.localize("ui.panel.config.zha.title")}> <hass-subpage
.header=${this.hass.localize("ui.panel.config.zha.title")}
.showBackButton=${this.isWide}
>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}> <ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header"> <div slot="header">
${this.hass.localize("ui.panel.config.zha.header")} ${this.hass.localize("ui.panel.config.zha.header")}

View File

@ -50,6 +50,7 @@ export class ZHADevicePage extends LitElement {
return html` return html`
<hass-subpage <hass-subpage
.header=${this.hass!.localize("ui.panel.config.zha.devices.header")} .header=${this.hass!.localize("ui.panel.config.zha.devices.header")}
.back=${!this.isWide}
> >
<zha-node <zha-node
.isWide="${this.isWide}" .isWide="${this.isWide}"

View File

@ -76,10 +76,14 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
color: grey; color: grey;
} }
[hidden] { ha-service-description[hidden] {
display: none; display: none;
} }
ha-paper-icon-button-arrow-prev[hide] {
visibility: hidden;
}
.toggle-help-icon { .toggle-help-icon {
position: absolute; position: absolute;
top: -6px; top: -6px;
@ -91,6 +95,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
<app-header slot="header" fixed=""> <app-header slot="header" fixed="">
<app-toolbar> <app-toolbar>
<ha-paper-icon-button-arrow-prev <ha-paper-icon-button-arrow-prev
hide$="[[isWide]]"
on-click="_backTapped" on-click="_backTapped"
></ha-paper-icon-button-arrow-prev> ></ha-paper-icon-button-arrow-prev>
<div main-title=""> <div main-title="">

View File

@ -673,7 +673,7 @@
"confirm_unsaved": "You have unsaved changes. Are you sure you want to leave?" "confirm_unsaved": "You have unsaved changes. Are you sure you want to leave?"
} }
}, },
"area_registry": { "areas": {
"caption": "Areas", "caption": "Areas",
"description": "Overview of all areas in your home.", "description": "Overview of all areas in your home.",
"picker": { "picker": {
@ -1253,7 +1253,7 @@
"battery": "Battery" "battery": "Battery"
} }
}, },
"entity_registry": { "entities": {
"caption": "Entities", "caption": "Entities",
"description": "Overview of all known entities.", "description": "Overview of all known entities.",
"picker": { "picker": {