Change config navigation to tabs (#4630)

* Change config navigation to tabs

* Update ha-menu-button.ts

* Icons

* update

* Review comments

* configSections -> object instead of array
This commit is contained in:
Bram Kragten 2020-01-28 21:48:21 +01:00 committed by GitHub
parent 6e624b394b
commit 1c9eab7ca0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1322 additions and 948 deletions

View File

@ -8,7 +8,7 @@ import { DEFAULT_DOMAIN_ICON } from "../const";
const fixedIcons = {
alert: "hass:alert",
alexa: "hass:amazon-alexa",
automation: "hass:playlist-play",
automation: "hass:robot",
calendar: "hass:calendar",
camera: "hass:video",
climate: "hass:thermostat",
@ -36,8 +36,8 @@ const fixedIcons = {
plant: "hass:flower",
proximity: "hass:apple-safari",
remote: "hass:remote",
scene: "hass:google-pages",
script: "hass:file-document",
scene: "hass:palette",
script: "hass:script-text",
sensor: "hass:eye",
simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny",
@ -48,7 +48,7 @@ const fixedIcons = {
water_heater: "hass:thermometer",
weather: "hass:weather-cloudy",
weblink: "hass:open-in-new",
zone: "hass:map-marker",
zone: "hass:map-marker-radius",
};
export const domainIcon = (domain: string, state?: string): string => {

View File

@ -135,7 +135,7 @@ class HaMenuButton extends LitElement {
top: 5px;
right: 2px;
border-radius: 50%;
border: 2px solid var(--primary-color);
border: 2px solid var(--app-header-background-color);
}
`;
}

View File

@ -50,6 +50,11 @@ class HassLoadingScreen extends LitElement {
return [
haStyle,
css`
:host {
display: block;
height: 100%;
background-color: var(--primary-background-color);
}
.content {
height: calc(100% - 64px);
display: flex;

View File

@ -59,6 +59,7 @@ class HassSubpage extends LitElement {
background-color: var(--app-header-background-color);
font-weight: 400;
color: var(--app-header-text-color, white);
border-bottom: var(--app-header-border-bottom, none);
}
ha-menu-button,

View File

@ -0,0 +1,239 @@
import {
LitElement,
property,
TemplateResult,
html,
customElement,
css,
CSSResult,
PropertyValues,
} from "lit-element";
import "../components/ha-menu-button";
import "../components/ha-paper-icon-button-arrow-prev";
import { classMap } from "lit-html/directives/class-map";
import { Route, HomeAssistant } from "../types";
import { navigate } from "../common/navigate";
import "@material/mwc-ripple";
import { isComponentLoaded } from "../common/config/is_component_loaded";
export interface PageNavigation {
path: string;
translationKey?: string;
component?: string;
name?: string;
core?: boolean;
exportOnly?: boolean;
icon?: string;
info?: any;
}
@customElement("hass-tabs-subpage")
class HassTabsSubpage extends LitElement {
@property() public hass!: HomeAssistant;
@property({ type: String, attribute: "back-path" }) public backPath?: string;
@property() public backCallback?: () => void;
@property({ type: Boolean }) public hassio = false;
@property({ type: Boolean }) public showAdvanced = false;
@property() public route!: Route;
@property() public tabs!: PageNavigation[];
@property({ type: Boolean, reflect: true }) public narrow = false;
@property() private _activeTab: number = -1;
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has("route")) {
this._activeTab = this.tabs.findIndex((tab) =>
this.route.prefix.includes(tab.path)
);
}
}
protected render(): TemplateResult {
return html`
<div class="toolbar">
<ha-paper-icon-button-arrow-prev
aria-label="Back"
.hassio=${this.hassio}
@click=${this._backTapped}
></ha-paper-icon-button-arrow-prev>
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
${this.tabs.map((page, index) =>
(!page.component ||
page.core ||
isComponentLoaded(this.hass, page.component)) &&
(!page.exportOnly || this.showAdvanced)
? html`
<div
class="tab ${classMap({
active: index === this._activeTab,
})}"
@click=${this._tabTapped}
.path=${page.path}
>
${this.narrow
? html`
<ha-icon .icon=${page.icon}></ha-icon>
`
: ""}
${!this.narrow || index === this._activeTab
? html`
<span class="name"
>${page.translationKey
? this.hass.localize(page.translationKey)
: name}</span
>
`
: ""}
<mwc-ripple></mwc-ripple>
</div>
`
: ""
)}
</div>
<div id="toolbar-icon">
<slot name="toolbar-icon"></slot>
</div>
</div>
<div class="content">
<slot></slot>
</div>
`;
}
private _tabTapped(ev: MouseEvent): void {
navigate(this, (ev.currentTarget as any).path, true);
}
private _backTapped(): void {
if (this.backPath) {
navigate(this, this.backPath);
return;
}
if (this.backCallback) {
this.backCallback();
return;
}
history.back();
}
static get styles(): CSSResult {
return css`
:host {
display: block;
height: 100%;
background-color: var(--primary-background-color);
}
.toolbar {
display: flex;
align-items: center;
font-size: 20px;
height: 64px;
background-color: var(--sidebar-background-color);
font-weight: 400;
color: var(--sidebar-text-color);
border-bottom: 1px solid var(--divider-color);
padding: 0 16px;
box-sizing: border-box;
}
:host([narrow]) .toolbar {
background-color: var(--primary-background-color);
border-bottom: none;
}
#tabbar {
display: flex;
font-size: 14px;
}
#tabbar.bottom-bar {
position: absolute;
bottom: 0;
left: 0;
padding: 0 16px;
box-sizing: border-box;
background-color: var(--sidebar-background-color);
border-top: 1px solid var(--divider-color);
justify-content: space-between;
z-index: 1;
font-size: 12px;
width: 100%;
}
#tabbar:not(.bottom-bar) {
margin: auto;
left: 50%;
position: absolute;
transform: translate(-50%, 0);
}
.tab {
padding: 0 32px;
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
justify-content: center;
height: 64px;
cursor: pointer;
}
.name {
white-space: nowrap;
}
.tab.active {
color: var(--primary-color);
}
#tabbar:not(.bottom-bar) .tab.active {
border-bottom: 2px solid var(--primary-color);
}
.bottom-bar .tab {
padding: 0 16px;
width: 20%;
min-width: 0;
}
ha-menu-button,
ha-paper-icon-button-arrow-prev,
::slotted([slot="toolbar-icon"]) {
pointer-events: auto;
color: var(--sidebar-icon-color);
}
[main-title] {
margin: 0 0 0 24px;
line-height: 20px;
flex-grow: 1;
}
.content {
position: relative;
width: 100%;
height: calc(100% - 64px);
overflow-y: auto;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
#toolbar-icon {
position: absolute;
right: 16px;
}
:host([narrow]) .content {
height: calc(100% - 128px);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hass-tabs-subpage": HassTabsSubpage;
}
}

View File

@ -10,7 +10,7 @@ import {
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import {
AreaRegistryEntry,
updateAreaRegistryEntry,
@ -20,7 +20,7 @@ import {
} from "../../../data/area_registry";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../layouts/hass-loading-screen";
import "../ha-config-section";
import {
@ -30,11 +30,14 @@ import {
import { classMap } from "lit-html/directives/class-map";
import { computeRTL } from "../../../common/util/compute_rtl";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-areas")
export class HaConfigAreas extends LitElement {
@property() public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() private _areas?: AreaRegistryEntry[];
private _unsubAreas?: UnsubscribeFunc;
@ -52,9 +55,12 @@ export class HaConfigAreas extends LitElement {
`;
}
return html`
<hass-subpage
.header="${this.hass.localize("ui.panel.config.areas.caption")}"
.showBackButton=${!this.isWide}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.persons}
>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
@ -95,10 +101,11 @@ export class HaConfigAreas extends LitElement {
: html``}
</ha-card>
</ha-config-section>
</hass-subpage>
</hass-tabs-subpage>
<ha-fab
?is-wide=${this.isWide}
?narrow=${this.narrow}
icon="hass:plus"
title="${this.hass.localize("ui.panel.config.areas.create_area")}"
@click=${this._createArea}
@ -162,6 +169,10 @@ All devices in this area will become unassigned.`)
static get styles(): CSSResult {
return css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
a {
color: var(--primary-color);
}
@ -189,7 +200,9 @@ All devices in this area will become unassigned.`)
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab.rtl {
right: auto;
left: 16px;

View File

@ -11,7 +11,6 @@ import {
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-fab";
@ -31,15 +30,19 @@ import {
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/ha-app-layout";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import "./action/ha-automation-action";
import "./condition/ha-automation-condition";
import "./trigger/ha-automation-trigger";
import "../../../layouts/hass-tabs-subpage";
import { configSections } from "../ha-panel-config";
export class HaAutomationEditor extends LitElement {
@property() public hass!: HomeAssistant;
@property() public automation!: AutomationEntity;
@property() public isWide?: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() public creatingNew?: boolean;
@property() private _config?: AutomationConfig;
@property() private _dirty?: boolean;
@ -47,169 +50,159 @@ export class HaAutomationEditor extends LitElement {
protected render(): TemplateResult {
return html`
<ha-app-layout has-scrolling-region>
<app-header slot="header" fixed>
<app-toolbar>
<ha-paper-icon-button-arrow-prev
@click=${this._backTapped}
></ha-paper-icon-button-arrow-prev>
<div main-title>
${this.automation
? computeStateName(this.automation)
: this.hass.localize(
"ui.panel.config.automation.editor.default_name"
)}
</div>
${this.creatingNew
? ""
: html`
<paper-icon-button
title="${this.hass.localize(
"ui.panel.config.automation.picker.delete_automation"
)}"
icon="hass:delete"
@click=${this._deleteConfirm}
></paper-icon-button>
`}
</app-toolbar>
</app-header>
<div class="content">
${this._errors
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.backCallback=${() => this._backTapped()}
.tabs=${configSections.automation}
>
${this.creatingNew
? ""
: html`
<paper-icon-button
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.automation.picker.delete_automation"
)}"
icon="hass:delete"
@click=${this._deleteConfirm}
></paper-icon-button>
`}
${this._errors
? html`
<div class="errors">${this._errors}</div>
`
: ""}
<div
class="${classMap({
rtl: computeRTL(this.hass),
})}"
>
${this._config
? html`
<div class="errors">${this._errors}</div>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">${this._config.alias}</span>
<span slot="introduction">
${this.hass.localize(
"ui.panel.config.automation.editor.introduction"
)}
</span>
<ha-card>
<div class="card-content">
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.alias"
)}
name="alias"
.value=${this._config.alias}
@value-changed=${this._valueChanged}
>
</paper-input>
<ha-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.description.label"
)}
.placeholder=${this.hass.localize(
"ui.panel.config.automation.editor.description.placeholder"
)}
name="description"
.value=${this._config.description}
@value-changed=${this._valueChanged}
></ha-textarea>
</div>
</ha-card>
</ha-config-section>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.header"
)}
</span>
<span slot="introduction">
<p>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.introduction"
)}
</p>
<a
href="https://home-assistant.io/docs/automation/trigger/"
target="_blank"
>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.learn_more"
)}
</a>
</span>
<ha-automation-trigger
.triggers=${this._config.trigger}
@value-changed=${this._triggerChanged}
.hass=${this.hass}
></ha-automation-trigger>
</ha-config-section>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.header"
)}
</span>
<span slot="introduction">
<p>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.introduction"
)}
</p>
<a
href="https://home-assistant.io/docs/scripts/conditions/"
target="_blank"
>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.learn_more"
)}
</a>
</span>
<ha-automation-condition
.conditions=${this._config.condition || []}
@value-changed=${this._conditionChanged}
.hass=${this.hass}
></ha-automation-condition>
</ha-config-section>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.header"
)}
</span>
<span slot="introduction">
<p>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.introduction"
)}
</p>
<a
href="https://home-assistant.io/docs/automation/action/"
target="_blank"
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.learn_more"
)}
</a>
</span>
<ha-automation-action
.actions=${this._config.action}
@value-changed=${this._actionChanged}
.hass=${this.hass}
></ha-automation-action>
</ha-config-section>
`
: ""}
<div
class="${classMap({
rtl: computeRTL(this.hass),
})}"
>
${this._config
? html`
<ha-config-section .isWide=${this.isWide}>
<span slot="header">${this._config.alias}</span>
<span slot="introduction">
${this.hass.localize(
"ui.panel.config.automation.editor.introduction"
)}
</span>
<ha-card>
<div class="card-content">
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.alias"
)}
name="alias"
.value=${this._config.alias}
@value-changed=${this._valueChanged}
>
</paper-input>
<ha-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.description.label"
)}
.placeholder=${this.hass.localize(
"ui.panel.config.automation.editor.description.placeholder"
)}
name="description"
.value=${this._config.description}
@value-changed=${this._valueChanged}
></ha-textarea>
</div>
</ha-card>
</ha-config-section>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.header"
)}
</span>
<span slot="introduction">
<p>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.introduction"
)}
</p>
<a
href="https://home-assistant.io/docs/automation/trigger/"
target="_blank"
>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.learn_more"
)}
</a>
</span>
<ha-automation-trigger
.triggers=${this._config.trigger}
@value-changed=${this._triggerChanged}
.hass=${this.hass}
></ha-automation-trigger>
</ha-config-section>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.header"
)}
</span>
<span slot="introduction">
<p>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.introduction"
)}
</p>
<a
href="https://home-assistant.io/docs/scripts/conditions/"
target="_blank"
>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.learn_more"
)}
</a>
</span>
<ha-automation-condition
.conditions=${this._config.condition || []}
@value-changed=${this._conditionChanged}
.hass=${this.hass}
></ha-automation-condition>
</ha-config-section>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.header"
)}
</span>
<span slot="introduction">
<p>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.introduction"
)}
</p>
<a
href="https://home-assistant.io/docs/automation/action/"
target="_blank"
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.learn_more"
)}
</a>
</span>
<ha-automation-action
.actions=${this._config.action}
@value-changed=${this._actionChanged}
.hass=${this.hass}
></ha-automation-action>
</ha-config-section>
`
: ""}
</div>
</div>
<ha-fab
slot="fab"
?is-wide="${this.isWide}"
?narrow="${this.narrow}"
?dirty="${this._dirty}"
icon="hass:content-save"
.title="${this.hass.localize(
@ -220,7 +213,7 @@ export class HaAutomationEditor extends LitElement {
rtl: computeRTL(this.hass),
})}"
></ha-fab>
</ha-app-layout>
</hass-tabs-subpage>
`;
}
@ -409,7 +402,10 @@ export class HaAutomationEditor extends LitElement {
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
margin-bottom: -140px;
}
ha-fab[dirty] {
margin-bottom: 0;
}

View File

@ -11,7 +11,7 @@ import { ifDefined } from "lit-html/directives/if-defined";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-tooltip/paper-tooltip";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../components/ha-card";
import "../../../components/ha-fab";
@ -22,7 +22,7 @@ import "../ha-config-section";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { computeRTL } from "../../../common/util/compute_rtl";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import {
AutomationEntity,
showAutomationEditor,
@ -32,18 +32,24 @@ import format_date_time from "../../../common/datetime/format_date_time";
import { fireEvent } from "../../../common/dom/fire_event";
import { showThingtalkDialog } from "./show-dialog-thingtalk";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { configSections } from "../ha-panel-config";
@customElement("ha-automation-picker")
class HaAutomationPicker extends LitElement {
@property() public hass!: HomeAssistant;
@property() public isWide!: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() public automations!: AutomationEntity[];
protected render(): TemplateResult {
return html`
<hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.automation.caption")}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.automation}
>
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
@ -150,6 +156,7 @@ class HaAutomationPicker extends LitElement {
<ha-fab
slot="fab"
?is-wide=${this.isWide}
?narrow=${this.narrow}
icon="hass:plus"
title=${this.hass.localize(
"ui.panel.config.automation.picker.add_automation"
@ -158,7 +165,7 @@ class HaAutomationPicker extends LitElement {
@click=${this._createNew}
></ha-fab>
</div>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -221,7 +228,9 @@ class HaAutomationPicker extends LitElement {
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab[rtl] {
right: auto;
left: 16px;

View File

@ -33,6 +33,8 @@ class HaConfigAutomation extends PolymerElement {
hass="[[hass]]"
automations="[[automations]]"
is-wide="[[isWide]]"
narrow="[[narrow]]"
route="[[route]]"
></ha-automation-picker>
</template>
@ -41,6 +43,8 @@ class HaConfigAutomation extends PolymerElement {
hass="[[hass]]"
automation="[[automation]]"
is-wide="[[isWide]]"
narrow="[[narrow]]"
route="[[route]]"
creating-new="[[_creatingNew]]"
></ha-automation-editor>
</template>
@ -52,6 +56,7 @@ class HaConfigAutomation extends PolymerElement {
hass: Object,
route: Object,
isWide: Boolean,
narrow: Boolean,
_routeData: Object,
_routeMatches: Boolean,
_creatingNew: Boolean,

View File

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

View File

@ -346,7 +346,7 @@ class CloudAlexa extends LitElement {
}
ha-card {
margin: 4px;
flex-basis: 300px;
width: 300px;
flex-grow: 1;
}
.card-content {

View File

@ -369,7 +369,7 @@ class CloudGoogleAssistant extends LitElement {
}
ha-card {
margin: 4px;
flex-basis: 300px;
width: 300px;
flex-grow: 1;
}
.card-content {

View File

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

View File

@ -4,11 +4,13 @@ import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../resources/ha-style";
import "./ha-config-section-core";
import { configSections } from "../ha-panel-config";
import LocalizeMixin from "../../../mixins/localize-mixin";
/*
@ -33,9 +35,13 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
}
</style>
<hass-subpage
header="[[localize('ui.panel.config.core.caption')]]"
show-back-button="[[!isWide]]"
<hass-tabs-subpage
hass="[[hass]]"
narrow="[[narrow]]"
route="[[route]]"
back-path="/config"
tabs="[[_computeTabs()]]"
show-advanced="[[showAdvanced]]"
>
<div class$="[[computeClasses(isWide)]]">
<ha-config-section-core
@ -44,7 +50,7 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
hass="[[hass]]"
></ha-config-section-core>
</div>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -52,10 +58,16 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
return {
hass: Object,
isWide: Boolean,
narrow: Boolean,
showAdvanced: Boolean,
route: Object,
};
}
_computeTabs() {
return configSections.general;
}
computeClasses(isWide) {
return isWide ? "content" : "content narrow";
}

View File

@ -1,10 +1,8 @@
import "@polymer/app-layout/app-header-layout/app-header-layout";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../layouts/hass-tabs-subpage";
import "../../../resources/ha-style";
import "../../../components/ha-paper-icon-button-arrow-prev";
@ -17,6 +15,8 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
import { sortStatesByName } from "../../../common/entity/states_sort_by_name";
import LocalizeMixin from "../../../mixins/localize-mixin";
import { configSections } from "../ha-panel-config";
/*
* @appliesMixin LocalizeMixin
*/
@ -29,19 +29,14 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
}
</style>
<app-header-layout has-scrolling-region="">
<app-header slot="header" fixed="">
<app-toolbar>
<ha-paper-icon-button-arrow-prev
hide$="[[isWide]]"
on-click="_backTapped"
></ha-paper-icon-button-arrow-prev>
<div main-title="">
[[localize('ui.panel.config.customize.caption')]]
</div>
</app-toolbar>
</app-header>
<hass-tabs-subpage
hass="[[hass]]"
narrow="[[narrow]]"
route="[[route]]"
back-path="/config"
tabs="[[_computeTabs()]]"
show-advanced="[[showAdvanced]]"
>
<div class$="[[computeClasses(isWide)]]">
<ha-config-section is-wide="[[isWide]]">
<span slot="header">
@ -59,7 +54,7 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
</ha-entity-config>
</ha-config-section>
</div>
</app-header-layout>
</hass-tabs-subpage>
`;
}
@ -67,7 +62,9 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
return {
hass: Object,
isWide: Boolean,
narrow: Boolean,
route: Object,
showAdvanced: Boolean,
entities: {
type: Array,
computed: "computeEntities(hass)",
@ -95,6 +92,10 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
history.back();
}
_computeTabs() {
return configSections.general;
}
computeEntities(hass) {
return Object.keys(hass.states)
.map((key) => hass.states[key])

View File

@ -15,7 +15,7 @@ import "../../../components/ha-menu-button";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
import { CloudStatus } from "../../../data/cloud";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-card";
@ -23,6 +23,7 @@ import "../../../components/ha-icon-next";
import "../ha-config-section";
import "./ha-config-navigation";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-dashboard")
class HaConfigDashboard extends LitElement {
@ -41,7 +42,6 @@ class HaConfigDashboard extends LitElement {
.hass=${this.hass}
.narrow=${this.narrow}
></ha-menu-button>
<div main-title>${this.hass.localize("panel.config")}</div>
</app-toolbar>
</app-header>
@ -57,68 +57,33 @@ class HaConfigDashboard extends LitElement {
${this.cloudStatus && isComponentLoaded(this.hass, "cloud")
? html`
<ha-card>
<a href="/config/cloud" tabindex="-1">
<paper-item>
<paper-item-body two-line="">
${this.hass.localize("ui.panel.config.cloud.caption")}
${this.cloudStatus.logged_in
? html`
<div secondary="">
${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(this.cloudStatus as CloudStatusLoggedIn)
.email
)}
</div>
`
: html`
<div secondary="">
${this.hass.localize(
"ui.panel.config.cloud.description_features"
)}
</div>
`}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this.showAdvanced}
.pages=${[
{
component: "cloud",
path: "/config/cloud",
translationKey: "ui.panel.config.cloud.caption",
info: this.cloudStatus,
icon: "hass:cloud-lock",
},
]}
></ha-config-navigation>
</ha-card>
`
: ""}
<ha-card>
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this.showAdvanced}
.pages=${[
{ page: "integrations", core: true },
{ page: "devices", core: true },
{ page: "entities", core: true },
{ page: "automation" },
{ page: "script" },
{ page: "scene" },
]}
></ha-config-navigation>
</ha-card>
<ha-card>
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this.showAdvanced}
.pages=${[
{ page: "core", core: true },
{ page: "server_control", core: true },
{ page: "areas", core: true },
{ page: "zone" },
{ page: "person" },
{ page: "users", core: true },
{ page: "zha" },
{ page: "zwave" },
{ page: "customize", core: true, advanced: true },
]}
></ha-config-navigation>
</ha-card>
${Object.values(configSections).map(
(section) => html`
<ha-card>
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this.showAdvanced}
.pages=${section}
></ha-config-navigation>
</ha-card>
`
)}
${!this.showAdvanced
? html`
<div class="promo-advanced">
@ -142,9 +107,15 @@ class HaConfigDashboard extends LitElement {
return [
haStyle,
css`
app-header {
--app-header-background-color: var(--primary-background-color);
}
ha-config-navigation:last-child {
margin-bottom: 24px;
}
ha-config-section {
margin-top: -20px;
}
ha-card {
overflow: hidden;
}

View File

@ -1,6 +1,6 @@
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-icon-item";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
@ -17,70 +17,65 @@ import {
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
export interface ConfigPageNavigation {
page: string;
core?: boolean;
advanced?: boolean;
info?: any;
}
import { PageNavigation } from "../../../layouts/hass-tabs-subpage";
@customElement("ha-config-navigation")
class HaConfigNavigation extends LitElement {
@property() public hass!: HomeAssistant;
@property() public showAdvanced!: boolean;
@property() public pages!: ConfigPageNavigation[];
@property() public curPage!: string;
@property() public pages!: PageNavigation[];
protected render(): TemplateResult {
return html`
<paper-listbox attr-for-selected="data-page" .selected=${this.curPage}>
${this.pages.map(({ page, core, advanced, info }) =>
(core || isComponentLoaded(this.hass, page)) &&
(!advanced || this.showAdvanced)
? html`
<a
href=${`/config/${page}`}
aria-role="option"
data-page="${page}"
tabindex="-1"
>
<paper-item>
<paper-item-body two-line>
${this.hass.localize(`ui.panel.config.${page}.caption`)}
${page === "cloud" && (info as CloudStatus)
? info.logged_in
? html`
<div secondary>
${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>
`
${this.pages.map((page) =>
(!page.component ||
page.core ||
isComponentLoaded(this.hass, page.component)) &&
(!page.exportOnly || this.showAdvanced)
? html`
<a
href=${`/config/${page.component}`}
aria-role="option"
tabindex="-1"
>
<paper-icon-item>
<ha-icon .icon=${page.icon} slot="item-icon"></ha-icon>
<paper-item-body two-line>
${this.hass.localize(
`ui.panel.config.${page.component}.caption`
)}
${page.component === "cloud" && (page.info as CloudStatus)
? page.info.logged_in
? html`
<div secondary>
${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(page.info as CloudStatusLoggedIn).email
)}
</div>
`
: html`
<div secondary>
${this.hass.localize(
`ui.panel.config.${page}.description`
"ui.panel.config.cloud.description_features"
)}
</div>
`}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
`
: ""
)}
</paper-listbox>
`
: html`
<div secondary>
${this.hass.localize(
`ui.panel.config.${page.component}.description`
)}
</div>
`}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-icon-item>
</a>
`
: ""
)}
`;
}
@ -93,6 +88,10 @@ class HaConfigNavigation extends LitElement {
display: block;
outline: 0;
}
ha-icon,
ha-icon-next {
color: var(--secondary-text-color);
}
.iron-selected paper-item::before,
a:not(.iron-selected):focus::before {
position: absolute;
@ -105,10 +104,6 @@ class HaConfigNavigation extends LitElement {
transition: opacity 15ms linear;
will-change: opacity;
}
.iron-selected paper-item::before {
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
}
a:not(.iron-selected):focus::before {
background-color: currentColor;
opacity: var(--dark-divider-opacity);
@ -117,12 +112,6 @@ class HaConfigNavigation extends LitElement {
.iron-selected:focus paper-item::before {
opacity: 0.2;
}
.iron-selected paper-item[pressed]::before {
opacity: 0.37;
}
paper-listbox {
padding: 0;
}
`;
}
}

View File

@ -9,7 +9,7 @@ import {
import memoizeOne from "memoize-one";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../layouts/hass-error-screen";
import "../ha-config-section";
@ -18,7 +18,7 @@ import "./device-detail/ha-device-triggers-card";
import "./device-detail/ha-device-conditions-card";
import "./device-detail/ha-device-actions-card";
import "./device-detail/ha-device-entities-card";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import { ConfigEntry } from "../../../data/config_entries";
import {
EntityRegistryEntry,
@ -45,6 +45,7 @@ import {
import { compare } from "../../../common/string/compare";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { createValidEntityId } from "../../../common/entity/valid_entity_id";
import { configSections } from "../ha-panel-config";
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string;
@ -60,6 +61,7 @@ export class HaConfigDevicePage extends LitElement {
@property() public deviceId!: string;
@property() public narrow!: boolean;
@property() public showAdvanced!: boolean;
@property() public route!: Route;
@property() private _triggers: DeviceTrigger[] = [];
@property() private _conditions: DeviceCondition[] = [];
@property() private _actions: DeviceAction[] = [];
@ -133,7 +135,12 @@ export class HaConfigDevicePage extends LitElement {
const entities = this._entities(this.deviceId, this.entities);
return html`
<hass-subpage .header=${device.name_by_user || device.name}>
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.tabs=${configSections.integrations}
.route=${this.route}
>
<paper-icon-button
slot="toolbar-icon"
icon="hass:settings"
@ -201,7 +208,7 @@ export class HaConfigDevicePage extends LitElement {
`
: html``}
</ha-config-section>
</hass-subpage>
</hass-tabs-subpage>
`;
}

View File

@ -1,4 +1,4 @@
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "./ha-devices-data-table";
import {
@ -10,11 +10,12 @@ import {
CSSResult,
css,
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import { DeviceRegistryEntry } from "../../../data/device_registry";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import { ConfigEntry } from "../../../data/config_entries";
import { AreaRegistryEntry } from "../../../data/area_registry";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-devices-dashboard")
export class HaConfigDeviceDashboard extends LitElement {
@ -26,12 +27,16 @@ export class HaConfigDeviceDashboard extends LitElement {
@property() public entities!: EntityRegistryEntry[];
@property() public areas!: AreaRegistryEntry[];
@property() public domain!: string;
@property() public route!: Route;
protected render(): TemplateResult {
return html`
<hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.devices.caption")}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.tabs=${configSections.integrations}
.route=${this.route}
>
<div class="content">
<ha-devices-data-table
@ -44,7 +49,7 @@ export class HaConfigDeviceDashboard extends LitElement {
.domain=${this.domain}
></ha-devices-data-table>
</div>
</hass-subpage>
</hass-tabs-subpage>
`;
}

View File

@ -100,6 +100,7 @@ class HaConfigDevices extends HassRouterPage {
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced;
pageEl.route = this.routeTail;
}
private _loadData() {

View File

@ -1,32 +1,29 @@
import "@polymer/paper-input/paper-input";
import { HassEntity } from "home-assistant-js-websocket";
import {
LitElement,
html,
css,
CSSResult,
TemplateResult,
property,
customElement,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input";
import "../../../components/ha-switch";
import { PolymerChangedEvent } from "../../../polymer-types";
import { HomeAssistant } from "../../../types";
import { HassEntity } from "home-assistant-js-websocket";
// tslint:disable-next-line: no-duplicate-imports
import { HaSwitch } from "../../../components/ha-switch";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-switch";
// tslint:disable-next-line: no-duplicate-imports
import { HaSwitch } from "../../../components/ha-switch";
import {
updateEntityRegistryEntry,
removeEntityRegistryEntry,
EntityRegistryEntry,
removeEntityRegistryEntry,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { fireEvent } from "../../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../../polymer-types";
import { HomeAssistant } from "../../../types";
@customElement("entity-registry-settings")
export class EntityRegistrySettings extends LitElement {

View File

@ -1,59 +1,59 @@
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
property,
query,
customElement,
} from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
import "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-tooltip/paper-tooltip";
import { HomeAssistant } from "../../../types";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
EntityRegistryEntry,
computeEntityRegistryName,
subscribeEntityRegistry,
removeEntityRegistryEntry,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-loading-screen";
import "../../../components/data-table/ha-data-table";
import "../../../components/ha-icon";
css,
CSSResult,
customElement,
html,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
import memoize from "memoize-one";
import { computeDomain } from "../../../common/entity/compute_domain";
import { domainIcon } from "../../../common/entity/domain_icon";
import { stateIcon } from "../../../common/entity/state_icon";
import { computeDomain } from "../../../common/entity/compute_domain";
import {
showEntityRegistryDetailDialog,
loadEntityRegistryDetailDialog,
} from "./show-dialog-entity-registry-detail";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import memoize from "memoize-one";
import "../../../components/data-table/ha-data-table";
// tslint:disable-next-line
import {
DataTableColumnContainer,
DataTableColumnData,
HaDataTable,
RowClickedEvent,
SelectionChangedEvent,
HaDataTable,
DataTableColumnData,
} from "../../../components/data-table/ha-data-table";
import "../../../components/ha-icon";
import {
computeEntityRegistryName,
EntityRegistryEntry,
removeEntityRegistryEntry,
subscribeEntityRegistry,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant, Route } from "../../../types";
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
import {
loadEntityRegistryDetailDialog,
showEntityRegistryDetailDialog,
} from "./show-dialog-entity-registry-detail";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-entities")
export class HaConfigEntities extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public isWide!: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() private _entities?: EntityRegistryEntry[];
@property() private _showDisabled = false;
@property() private _showUnavailable = true;
@ -224,9 +224,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
`;
}
return html`
<hass-subpage
.header="${this.hass.localize("ui.panel.config.entities.caption")}"
.showBackButton=${!this.isWide}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.integrations}
>
<div class="content">
<div class="intro">
@ -367,7 +370,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
</div>
</ha-data-table>
</div>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -490,6 +493,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
static get styles(): CSSResult {
return css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
a {
color: var(--primary-color);
}

View File

@ -1,177 +0,0 @@
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"
),
},
zone: {
tag: "ha-config-zone",
load: () =>
import(
/* webpackChunkName: "panel-config-zone" */ "./zone/ha-config-zone"
),
},
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,18 +1,59 @@
import { customElement } from "lit-element";
import { customElement, LitElement, html, css, property } from "lit-element";
import { classMap } from "lit-html/directives/class-map";
@customElement("ha-config-section")
export class HaConfigSection extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot!.innerHTML = `
<style>
export class HaConfigSection extends LitElement {
@property() public isWide: boolean = false;
protected render() {
return html`
<div
class="content ${classMap({
narrow: !this.isWide,
})}"
>
<div class="header"><slot name="header"></slot></div>
<div
class="together layout ${classMap({
narrow: !this.isWide,
vertical: !this.isWide,
horizontal: this.isWide,
})}"
>
<div class="intro"><slot name="introduction"></slot></div>
<div class="panel flex-auto"><slot></slot></div>
</div>
</div>
`;
}
static get styles() {
return css`
:host {
display: block;
}
.content {
padding: 28px 20px 0;
max-width: 640px;
max-width: 1040px;
margin: 0 auto;
}
.layout {
display: flex;
}
.horizontal {
flex-direction: row;
}
.vertical {
flex-direction: column;
}
.flex-auto {
flex: 1 1 auto;
}
.header {
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
@ -26,7 +67,7 @@ export class HaConfigSection extends HTMLElement {
}
.together {
margin-top: 20px;
margin-top: 32px;
}
.intro {
@ -37,8 +78,8 @@ export class HaConfigSection extends HTMLElement {
font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height);
width: 100%;
width: 100%;
max-width: 500px;
max-width: 400px;
margin-right: 40px;
opacity: var(--dark-primary-opacity);
font-size: 14px;
padding-bottom: 20px;
@ -52,14 +93,18 @@ export class HaConfigSection extends HTMLElement {
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>
.narrow.content {
max-width: 640px;
}
.narrow .together {
margin-top: 20px;
}
.narrow .intro {
padding-bottom: 20px;
margin-right: 0;
max-width: 500px;
}
`;
}
}

View File

@ -1,12 +1,4 @@
import {
property,
PropertyValues,
customElement,
LitElement,
html,
CSSResult,
css,
} from "lit-element";
import { property, PropertyValues, customElement } from "lit-element";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-item/paper-item";
import "../../layouts/hass-loading-screen";
@ -18,9 +10,9 @@ import {
getOptimisticFrontendUserDataCollection,
CoreFrontendUserData,
} from "../../data/frontend";
import "./ha-config-router";
import "./dashboard/ha-config-navigation";
import { classMap } from "lit-html/directives/class-map";
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
import { PolymerElement } from "@polymer/polymer";
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
declare global {
// for fire event
@ -29,14 +21,252 @@ declare global {
}
}
const NO_SIDEBAR_PAGES = ["zone"];
export const configSections: { [name: string]: PageNavigation[] } = {
integrations: [
{
component: "integrations",
path: "/config/integrations",
translationKey: "ui.panel.config.integrations.caption",
icon: "hass:puzzle",
core: true,
},
{
component: "devices",
path: "/config/devices",
translationKey: "ui.panel.config.devices.caption",
icon: "hass:devices",
core: true,
},
{
component: "entities",
path: "/config/entities",
translationKey: "ui.panel.config.entities.caption",
icon: "hass:shape",
core: true,
},
{
component: "areas",
path: "/config/areas",
translationKey: "ui.panel.config.areas.caption",
icon: "hass:sofa",
core: true,
},
],
automation: [
{
component: "automation",
path: "/config/automation",
translationKey: "ui.panel.config.automation.caption",
icon: "hass:robot",
},
{
component: "scene",
path: "/config/scene",
translationKey: "ui.panel.config.scene.caption",
icon: "hass:palette",
},
{
component: "script",
path: "/config/script",
translationKey: "ui.panel.config.script.caption",
icon: "hass:script-text",
},
],
persons: [
{
component: "person",
path: "/config/person",
translationKey: "ui.panel.config.person.caption",
icon: "hass:account",
},
{
component: "zone",
path: "/config/zone",
translationKey: "ui.panel.config.zone.caption",
icon: "hass:map-marker-radius",
core: true,
},
{
component: "users",
path: "/config/users",
translationKey: "ui.panel.config.users.caption",
icon: "hass:account-badge-horizontal",
core: true,
},
],
general: [
{
component: "core",
path: "/config/core",
translationKey: "ui.panel.config.core.caption",
icon: "hass:home-assistant",
core: true,
},
{
component: "server_control",
path: "/config/server_control",
translationKey: "ui.panel.config.server_control.caption",
icon: "hass:server",
core: true,
},
{
component: "customize",
path: "/config/customize",
translationKey: "ui.panel.config.customize.caption",
icon: "hass:pencil",
core: true,
exportOnly: true,
},
],
other: [
{
component: "zha",
path: "/config/zha",
translationKey: "ui.panel.config.zha.caption",
icon: "hass:zigbee",
},
{
component: "zwave",
path: "/config/zwave",
translationKey: "ui.panel.config.zwave.caption",
icon: "hass:z-wave",
},
],
};
@customElement("ha-panel-config")
class HaPanelConfig extends LitElement {
class HaPanelConfig extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public route!: Route;
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"
),
},
zone: {
tag: "ha-config-zone",
load: () =>
import(
/* webpackChunkName: "panel-config-zone" */ "./zone/ha-config-zone"
),
},
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 _wide: boolean = false;
@property() private _coreUserData?: CoreFrontendUserData;
@ -85,64 +315,42 @@ class HaPanelConfig extends LitElement {
this.addEventListener("ha-refresh-cloud-status", () =>
this._updateCloudStatus()
);
this.style.setProperty(
"--app-header-background-color",
"var(--sidebar-background-color)"
);
this.style.setProperty(
"--app-header-text-color",
"var(--sidebar-text-color)"
);
this.style.setProperty(
"--app-header-border-bottom",
"1px solid var(--divider-color)"
);
}
protected render() {
const dividerPos = this.route.path.indexOf("/", 1);
const curPage =
dividerPos === -1
? this.route.path.substr(1)
: this.route.path.substr(1, dividerPos - 1);
protected updatePageEl(el) {
const isWide =
this.hass.dockedSidebar === "docked" ? this._wideSidebar : this._wide;
const showSidebar = isWide && !NO_SIDEBAR_PAGES.includes(curPage);
return html`
${showSidebar
? html`
<div class="side-bar">
<div class="toolbar">Configuration</div>
<div class="navigation">
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this._showAdvanced}
.curPage=${curPage}
.pages=${[
{ page: "cloud", info: this._cloudStatus },
{ page: "integrations", core: true },
{ page: "devices", core: true },
{ page: "entities", core: true },
{ page: "automation" },
{ page: "script" },
{ page: "scene" },
{ page: "core", core: true },
{ page: "areas", core: true },
{ page: "zone" },
{ page: "person" },
{ page: "users", core: true },
{ page: "server_control", 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": showSidebar })}
></ha-config-router>
`;
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,
narrow: this.narrow,
cloudStatus: this._cloudStatus,
});
} else {
el.route = this.routeTail;
el.hass = this.hass;
el.showAdvanced = this._showAdvanced;
el.isWide = isWide;
el.narrow = this.narrow;
el.cloudStatus = this._cloudStatus;
}
}
private async _updateCloudStatus() {
@ -152,54 +360,6 @@ class HaPanelConfig extends LitElement {
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 var(--divider-color);
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 {

View File

@ -12,7 +12,7 @@ import "../../../components/ha-card";
import "../../../components/ha-icon-next";
import "../../../components/ha-fab";
import "../../../components/entity/ha-state-icon";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../resources/ha-style";
import "../../../components/ha-icon";
@ -38,18 +38,21 @@ import {
css,
CSSResult,
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import { ConfigEntry, deleteConfigEntry } from "../../../data/config_entries";
import { fireEvent } from "../../../common/dom/fire_event";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import { DataEntryFlowProgress } from "../../../data/data_entry_flow";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-entries-dashboard")
export class HaConfigManagerDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public showAdvanced!: boolean;
@property() public isWide!: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() private configEntries!: ConfigEntry[];
@ -72,9 +75,12 @@ export class HaConfigManagerDashboard extends LitElement {
protected render(): TemplateResult {
return html`
<hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.integrations.caption")}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.integrations}
>
<paper-menu-button
close-on-activate
@ -247,8 +253,9 @@ export class HaConfigManagerDashboard extends LitElement {
title=${this.hass.localize("ui.panel.config.integrations.new")}
@click=${this._createFlow}
?rtl=${computeRTL(this.hass!)}
?narrow=${this.narrow}
></ha-fab>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -361,7 +368,9 @@ export class HaConfigManagerDashboard extends LitElement {
right: 16px;
z-index: 1;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab[rtl] {
right: auto;
left: 16px;

View File

@ -104,7 +104,7 @@ class HaConfigIntegrations extends HassRouterPage {
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced;
pageEl.route = this.routeTail;
if (this._currentPage === "dashboard") {
pageEl.configEntriesInProgress = this._configEntriesInProgress;
return;

View File

@ -9,7 +9,7 @@ import {
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import {
Person,
fetchPersons,
@ -19,7 +19,7 @@ import {
} from "../../../data/person";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../layouts/hass-loading-screen";
import { compare } from "../../../common/string/compare";
import "../ha-config-section";
@ -28,10 +28,13 @@ import {
loadPersonDetailDialog,
} from "./show-dialog-person-detail";
import { User, fetchUsers } from "../../../data/user";
import { configSections } from "../ha-panel-config";
class HaConfigPerson extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() public narrow?: boolean;
@property() public route!: Route;
@property() private _storageItems?: Person[];
@property() private _configItems?: Person[];
private _usersLoad?: Promise<User[]>;
@ -48,9 +51,12 @@ class HaConfigPerson extends LitElement {
}
const hass = this.hass;
return html`
<hass-subpage
.header=${hass.localize("ui.panel.config.person.caption")}
.showBackButton=${!this.isWide}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
back-path="/config"
.tabs=${configSections.persons}
>
<ha-config-section .isWide=${this.isWide}>
<span slot="header"
@ -109,10 +115,11 @@ class HaConfigPerson extends LitElement {
`
: ""}
</ha-config-section>
</hass-subpage>
</hass-tabs-subpage>
<ha-fab
?is-wide=${this.isWide}
?narrow=${this.narrow}
icon="hass:plus"
title="${hass.localize("ui.panel.config.person.add_person")}"
@click=${this._createPerson}
@ -231,7 +238,9 @@ ${this.hass!.localize("ui.panel.config.person.confirm_delete2")}`)
right: 16px;
z-index: 1;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab[is-wide] {
bottom: 24px;
right: 24px;

View File

@ -54,6 +54,7 @@ class HaConfigScene extends HassRouterPage {
pageEl.hass = this.hass;
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.route = this.routeTail;
pageEl.showAdvanced = this.showAdvanced;
if (this.hass) {

View File

@ -10,7 +10,7 @@ import {
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-tooltip/paper-tooltip";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../components/ha-card";
import "../../../components/ha-fab";
@ -20,24 +20,29 @@ import "../ha-config-section";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { computeRTL } from "../../../common/util/compute_rtl";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import { SceneEntity, activateScene } from "../../../data/scene";
import { showToast } from "../../../util/toast";
import { ifDefined } from "lit-html/directives/if-defined";
import { forwardHaptic } from "../../../data/haptics";
import { configSections } from "../ha-panel-config";
@customElement("ha-scene-dashboard")
class HaSceneDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public route!: Route;
@property() public scenes!: SceneEntity[];
protected render(): TemplateResult {
return html`
<hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.scene.caption")}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.automation}
>
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
@ -72,8 +77,7 @@ class HaSceneDashboard extends LitElement {
`
: this.scenes.map(
(scene) => html`
<div class='scene'>
<div class="scene">
<paper-icon-button
.scene=${scene}
icon="hass:play"
@ -82,46 +86,44 @@ class HaSceneDashboard extends LitElement {
)}"
@click=${this._activateScene}
></paper-icon-button>
<paper-item-body two-line>
<div>${computeStateName(scene)}</div>
</paper-item-body>
<a
href=${ifDefined(
scene.attributes.id
? `/config/scene/edit/${scene.attributes.id}`
: undefined
)}
>
<paper-icon-button
title="${this.hass.localize(
"ui.panel.config.scene.picker.edit_scene"
)}"
icon="hass:pencil"
.disabled=${!scene.attributes.id}
></paper-icon-button>
${
!scene.attributes.id
? html`
<paper-tooltip position="left">
${this.hass.localize(
"ui.panel.config.scene.picker.only_editable"
)}
</paper-tooltip>
`
: ""
}
</a>
<paper-item-body two-line>
<div>${computeStateName(scene)}</div>
</paper-item-body>
<div class="actions">
<a
href=${ifDefined(
scene.attributes.id
? `/config/scene/edit/${scene.attributes.id}`
: undefined
)}
>
<paper-icon-button
title="${this.hass.localize(
"ui.panel.config.scene.picker.edit_scene"
)}"
icon="hass:pencil"
.disabled=${!scene.attributes.id}
></paper-icon-button>
${!scene.attributes.id
? html`
<paper-tooltip position="left">
${this.hass.localize(
"ui.panel.config.scene.picker.only_editable"
)}
</paper-tooltip>
`
: ""}
</a>
</div>
</a>
</div>
`
)}
</ha-card>
</ha-config-section>
<a href="/config/scene/edit/new">
<ha-fab
slot="fab"
?is-wide=${!this.narrow}
?is-wide=${this.isWide}
?narrow=${this.narrow}
icon="hass:plus"
title=${this.hass.localize(
"ui.panel.config.scene.picker.add_scene"
@ -129,7 +131,7 @@ class HaSceneDashboard extends LitElement {
?rtl=${computeRTL(this.hass)}
></ha-fab>
</a>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -152,13 +154,11 @@ class HaSceneDashboard extends LitElement {
css`
:host {
display: block;
}
hass-subpage {
min-height: 100vh;
height: 100%;
}
ha-card {
padding-bottom: 8px;
margin-bottom: 56px;
}
@ -173,6 +173,10 @@ class HaSceneDashboard extends LitElement {
color: var(--primary-text-color);
}
.actions {
display: flex;
}
ha-entity-toggle {
margin-right: 16px;
}
@ -188,7 +192,9 @@ class HaSceneDashboard extends LitElement {
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab[rtl] {
right: auto;
left: 16px;

View File

@ -26,7 +26,7 @@ import "../../../layouts/ha-app-layout";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import {
@ -55,6 +55,7 @@ import memoizeOne from "memoize-one";
import { computeDomain } from "../../../common/entity/compute_domain";
import { HassEvent } from "home-assistant-js-websocket";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { configSections } from "../ha-panel-config";
interface DeviceEntities {
id: string;
@ -69,7 +70,9 @@ interface DeviceEntitiesLookup {
@customElement("ha-scene-editor")
export class HaSceneEditor extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public route!: Route;
@property() public scene?: SceneEntity;
@property() public creatingNew?: boolean;
@property() public showAdvanced!: boolean;
@ -157,39 +160,36 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
this._deviceRegistryEntries
);
return html`
<ha-app-layout has-scrolling-region>
<app-header slot="header" fixed>
<app-toolbar>
<ha-paper-icon-button-arrow-prev
@click=${this._backTapped}
></ha-paper-icon-button-arrow-prev>
<div main-title>
${this.scene
? computeStateName(this.scene)
: this.hass.localize(
"ui.panel.config.scene.editor.default_name"
)}
</div>
${this.creatingNew
? ""
: html`
<paper-icon-button
title="${this.hass.localize(
"ui.panel.config.scene.picker.delete_scene"
)}"
icon="hass:delete"
@click=${this._deleteTapped}
></paper-icon-button>
`}
</app-toolbar>
</app-header>
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.backCallback=${() => this._backTapped()}
.tabs=${configSections.automation}
>
<div class="content">
${this._errors
? html`
<div class="errors">${this._errors}</div>
`
: ""}
${
this.creatingNew
? ""
: html`
<paper-icon-button
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.scene.picker.delete_scene"
)}"
icon="hass:delete"
@click=${this._deleteTapped}
></paper-icon-button>
`
}
${
this._errors
? html`
<div class="errors">${this._errors}</div>
`
: ""
}
<div
id="root"
class="${classMap({
@ -198,11 +198,13 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
>
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
${this.scene
? computeStateName(this.scene)
: this.hass.localize(
"ui.panel.config.scene.editor.default_name"
)}
${
this.scene
? computeStateName(this.scene)
: this.hass.localize(
"ui.panel.config.scene.editor.default_name"
)
}
</div>
<div slot="introduction">
${this.hass.localize(
@ -291,87 +293,88 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
</ha-card>
</ha-config-section>
${this.showAdvanced
? html`
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize(
"ui.panel.config.scene.editor.entities.header"
)}
</div>
<div slot="introduction">
${this.hass.localize(
"ui.panel.config.scene.editor.entities.introduction"
)}
</div>
${entities.length
? html`
<ha-card
class="entities"
.header=${this.hass.localize(
"ui.panel.config.scene.editor.entities.without_device"
)}
>
${entities.map((entityId) => {
const stateObj = this.hass.states[entityId];
if (!stateObj) {
return html``;
}
return html`
<paper-icon-item
.entityId=${entityId}
@click=${this._showMoreInfo}
class="device-entity"
>
<state-badge
.stateObj=${stateObj}
slot="item-icon"
></state-badge>
<paper-item-body>
${computeStateName(stateObj)}
</paper-item-body>
<paper-icon-button
icon="hass:delete"
.entityId=${entityId}
.title="${this.hass.localize(
"ui.panel.config.scene.editor.entities.delete"
)}"
@click=${this._deleteEntity}
></paper-icon-button>
</paper-icon-item>
`;
})}
</ha-card>
`
: ""}
<ha-card
header=${this.hass.localize(
"ui.panel.config.scene.editor.entities.add"
)}
>
<div class="card-content">
${
this.showAdvanced
? html`
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize(
"ui.panel.config.scene.editor.entities.device_entities"
"ui.panel.config.scene.editor.entities.header"
)}
<ha-entity-picker
@value-changed=${this._entityPicked}
.excludeDomains=${SCENE_IGNORED_DOMAINS}
.hass=${this.hass}
label=${this.hass.localize(
"ui.panel.config.scene.editor.entities.add"
)}
/>
</div>
</ha-card>
</ha-config-section>
`
: ""}
<div slot="introduction">
${this.hass.localize(
"ui.panel.config.scene.editor.entities.introduction"
)}
</div>
${entities.length
? html`
<ha-card
class="entities"
.header=${this.hass.localize(
"ui.panel.config.scene.editor.entities.without_device"
)}
>
${entities.map((entityId) => {
const stateObj = this.hass.states[entityId];
if (!stateObj) {
return html``;
}
return html`
<paper-icon-item
.entityId=${entityId}
@click=${this._showMoreInfo}
class="device-entity"
>
<state-badge
.stateObj=${stateObj}
slot="item-icon"
></state-badge>
<paper-item-body>
${computeStateName(stateObj)}
</paper-item-body>
<paper-icon-button
icon="hass:delete"
.entityId=${entityId}
.title="${this.hass.localize(
"ui.panel.config.scene.editor.entities.delete"
)}"
@click=${this._deleteEntity}
></paper-icon-button>
</paper-icon-item>
`;
})}
</ha-card>
`
: ""}
<ha-card
header=${this.hass.localize(
"ui.panel.config.scene.editor.entities.add"
)}
>
<div class="card-content">
${this.hass.localize(
"ui.panel.config.scene.editor.entities.device_entities"
)}
<ha-entity-picker
@value-changed=${this._entityPicked}
.excludeDomains=${SCENE_IGNORED_DOMAINS}
.hass=${this.hass}
label=${this.hass.localize(
"ui.panel.config.scene.editor.entities.add"
)}
/>
</div>
</ha-card>
</ha-config-section>
`
: ""
}
</div>
</div>
<ha-fab
slot="fab"
?is-wide="${this.isWide}"
?narrow="${this.narrow}"
?dirty="${this._dirty}"
icon="hass:content-save"
.title="${this.hass.localize("ui.panel.config.scene.editor.save")}"
@ -706,7 +709,10 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
margin-bottom: -140px;
}
ha-fab[dirty] {
margin-bottom: 0;
}

View File

@ -34,6 +34,8 @@ class HaConfigScript extends PolymerElement {
hass="[[hass]]"
scripts="[[scripts]]"
is-wide="[[isWide]]"
narrow="[[narrow]]"
route="[[route]]"
></ha-script-picker>
</template>
@ -42,6 +44,8 @@ class HaConfigScript extends PolymerElement {
hass="[[hass]]"
script="[[script]]"
is-wide="[[isWide]]"
narrow="[[narrow]]"
route="[[route]]"
creating-new="[[_creatingNew]]"
></ha-script-editor>
</template>
@ -53,6 +57,7 @@ class HaConfigScript extends PolymerElement {
hass: Object,
route: Object,
isWide: Boolean,
narrow: Boolean,
_routeData: Object,
_routeMatches: Boolean,
_creatingNew: Boolean,

View File

@ -11,7 +11,6 @@ import {
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-fab";
@ -25,14 +24,17 @@ import {
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/ha-app-layout";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import "../automation/action/ha-automation-action";
import { computeObjectId } from "../../../common/entity/compute_object_id";
import { configSections } from "../ha-panel-config";
export class HaScriptEditor extends LitElement {
@property() public hass!: HomeAssistant;
@property() public script!: ScriptEntity;
@property() public isWide?: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() public creatingNew?: boolean;
@property() private _config?: ScriptConfig;
@property() private _dirty?: boolean;
@ -40,32 +42,25 @@ export class HaScriptEditor extends LitElement {
protected render(): TemplateResult {
return html`
<ha-app-layout has-scrolling-region>
<app-header slot="header" fixed>
<app-toolbar>
<ha-paper-icon-button-arrow-prev
@click=${this._backTapped}
></ha-paper-icon-button-arrow-prev>
<div main-title>
${this.script
? computeStateName(this.script)
: this.hass.localize(
"ui.panel.config.script.editor.default_name"
)}
</div>
${this.creatingNew
? ""
: html`
<paper-icon-button
title="${this.hass.localize(
"ui.panel.config.script.editor.delete_script"
)}"
icon="hass:delete"
@click=${this._deleteConfirm}
></paper-icon-button>
`}
</app-toolbar>
</app-header>
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.backCallback=${() => this._backTapped()}
.tabs=${configSections.automation}
>
${this.creatingNew
? ""
: html`
<paper-icon-button
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.script.editor.delete_script"
)}"
icon="hass:delete"
@click=${this._deleteConfirm}
></paper-icon-button>
`}
<div class="content">
${this._errors
@ -134,9 +129,9 @@ export class HaScriptEditor extends LitElement {
</div>
</div>
<ha-fab
slot="fab"
?is-wide="${this.isWide}"
?dirty="${this._dirty}"
?is-wide=${this.isWide}
?narrow=${this.narrow}
?dirty=${this._dirty}
icon="hass:content-save"
.title="${this.hass.localize("ui.common.save")}"
@click=${this._saveScript}
@ -144,7 +139,7 @@ export class HaScriptEditor extends LitElement {
rtl: computeRTL(this.hass),
})}"
></ha-fab>
</ha-app-layout>
</hass-tabs-subpage>
`;
}
@ -301,7 +296,10 @@ export class HaScriptEditor extends LitElement {
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
margin-bottom: -140px;
}
ha-fab[dirty] {
margin-bottom: 0;
}

View File

@ -11,7 +11,7 @@ import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item-body";
import { HassEntity } from "home-assistant-js-websocket";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import { computeRTL } from "../../../common/util/compute_rtl";
@ -22,21 +22,27 @@ import "../ha-config-section";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import { triggerScript } from "../../../data/script";
import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config";
@customElement("ha-script-picker")
class HaScriptPicker extends LitElement {
@property() public hass!: HomeAssistant;
@property() public scripts!: HassEntity[];
@property() public isWide!: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
protected render(): TemplateResult {
return html`
<hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.script.caption")}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.automation}
>
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
@ -99,8 +105,8 @@ class HaScriptPicker extends LitElement {
<a href="/config/script/new">
<ha-fab
slot="fab"
?is-wide=${this.isWide}
?narrow=${this.narrow}
icon="hass:plus"
title="${this.hass.localize(
"ui.panel.config.script.picker.add_script"
@ -108,7 +114,7 @@ class HaScriptPicker extends LitElement {
?rtl=${computeRTL(this.hass)}
></ha-fab>
</a>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -169,7 +175,9 @@ class HaScriptPicker extends LitElement {
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab[rtl] {
right: auto;
left: 16px;

View File

@ -4,12 +4,13 @@ import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../resources/ha-style";
import "./ha-config-section-server-control";
import LocalizeMixin from "../../../mixins/localize-mixin";
import { configSections } from "../ha-panel-config";
/*
* @appliesMixin LocalizeMixin
@ -33,9 +34,13 @@ class HaConfigServerControl extends LocalizeMixin(PolymerElement) {
}
</style>
<hass-subpage
header="[[localize('ui.panel.config.server_control.caption')]]"
show-back-button="[[!isWide]]"
<hass-tabs-subpage
hass="[[hass]]"
narrow="[[narrow]]"
route="[[route]]"
back-path="/config"
tabs="[[_computeTabs()]]"
show-advanced="[[showAdvanced]]"
>
<div class$="[[computeClasses(isWide)]]">
<ha-config-section-server-control
@ -44,7 +49,7 @@ class HaConfigServerControl extends LocalizeMixin(PolymerElement) {
hass="[[hass]]"
></ha-config-section-server-control>
</div>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -52,10 +57,16 @@ class HaConfigServerControl extends LocalizeMixin(PolymerElement) {
return {
hass: Object,
isWide: Boolean,
narrow: Boolean,
route: Object,
showAdvanced: Boolean,
};
}
_computeTabs() {
return configSections.general;
}
computeClasses(isWide) {
return isWide ? "content" : "content narrow";
}

View File

@ -3,7 +3,7 @@ import "@polymer/paper-item/paper-item-body";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../components/ha-icon-next";
import "../../../components/ha-card";
import "../../../components/ha-fab";
@ -13,6 +13,7 @@ import NavigateMixin from "../../../mixins/navigate-mixin";
import { EventsMixin } from "../../../mixins/events-mixin";
import { computeRTL } from "../../../common/util/compute_rtl";
import { configSections } from "../ha-panel-config";
let registeredDialog = false;
@ -41,6 +42,9 @@ class HaUserPicker extends EventsMixin(
right: auto;
left: 16px;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab[rtl][is-wide] {
bottom: 24px;
right: auto;
@ -58,9 +62,12 @@ class HaUserPicker extends EventsMixin(
}
</style>
<hass-subpage
header="[[localize('ui.panel.config.users.picker.title')]]"
show-back-button="[[!isWide]]"
<hass-tabs-subpage
hass="[[hass]]"
narrow="[[narrow]]"
route="[[route]]"
back-path="/config"
tabs="[[_computeTabs()]]"
>
<ha-card>
<template is="dom-repeat" items="[[users]]" as="user">
@ -84,12 +91,13 @@ class HaUserPicker extends EventsMixin(
<ha-fab
is-wide$="[[isWide]]"
narrow$="[[narrow]]"
icon="hass:plus"
title="[[localize('ui.panel.config.users.picker.add_user')]]"
on-click="_addUser"
rtl$="[[rtl]]"
></ha-fab>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -98,6 +106,8 @@ class HaUserPicker extends EventsMixin(
hass: Object,
users: Array,
isWide: Boolean,
narrow: Boolean,
route: Object,
rtl: {
type: Boolean,
reflectToAttribute: true,
@ -138,6 +148,10 @@ class HaUserPicker extends EventsMixin(
return computeRTL(hass);
}
_computeTabs() {
return configSections.persons;
}
_addUser() {
this.fire("show-add-user", {
hass: this.hass,

View File

@ -28,6 +28,8 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
hass="[[hass]]"
users="[[_users]]"
is-wide="[[isWide]]"
narrow="[[narrow]]"
route="[[route]]"
></ha-config-user-picker>
</template>
<template
@ -38,6 +40,8 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
<ha-user-editor
hass="[[hass]]"
user="[[_computeUser(_users, _routeData.user)]]"
narrow="[[narrow]]"
route="[[route]]"
></ha-user-editor>
</template>
`;
@ -47,6 +51,7 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
return {
hass: Object,
isWide: Boolean,
narrow: Boolean,
route: {
type: Object,
observer: "_checkRoute",

View File

@ -10,10 +10,10 @@ import {
import { until } from "lit-html/directives/until";
import "@material/mwc-button";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles";
import "../../../components/ha-card";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import {
@ -29,6 +29,7 @@ import {
showConfirmationDialog,
showPromptDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { configSections } from "../ha-panel-config";
declare global {
interface HASSDomEvents {
@ -42,6 +43,8 @@ const GROUPS = [SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_ADMIN];
class HaUserEditor extends LitElement {
@property() public hass?: HomeAssistant;
@property() public user?: User;
@property() public narrow?: boolean;
@property() public route!: Route;
protected render(): TemplateResult {
const hass = this.hass;
@ -51,8 +54,11 @@ class HaUserEditor extends LitElement {
}
return html`
<hass-subpage
.header=${hass.localize("ui.panel.config.users.editor.caption")}
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${configSections.persons}
>
<ha-card .header=${this._name}>
<table class="card-content">
@ -130,7 +136,7 @@ class HaUserEditor extends LitElement {
: ""}
</div>
</ha-card>
</hass-subpage>
</hass-tabs-subpage>
`;
}
@ -225,7 +231,7 @@ class HaUserEditor extends LitElement {
}
ha-card {
max-width: 600px;
margin: 0 auto 16px;
margin: 16px auto 16px;
}
hass-subpage ha-card:first-of-type {
direction: ltr;

View File

@ -16,10 +16,10 @@ import "@polymer/paper-tooltip/paper-tooltip";
import "../../../components/map/ha-locations-editor";
import { HomeAssistant } from "../../../types";
import { HomeAssistant, Route } from "../../../types";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import "../../../layouts/hass-loading-screen";
import { compare } from "../../../common/string/compare";
import "../ha-config-section";
@ -42,12 +42,14 @@ import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { subscribeEntityRegistry } from "../../../data/entity_registry";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-zone")
export class HaConfigZone extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() public narrow?: boolean;
@property() public route!: Route;
@property() private _storageItems?: Zone[];
@property() private _stateItems?: HassEntity[];
@property() private _activeEntry: string = "";
@ -179,7 +181,13 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
`;
return html`
<hass-subpage .header=${hass.localize("ui.panel.config.zone.caption")}>
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
back-path="/config"
.tabs=${configSections.persons}
>
${this.narrow
? html`
<ha-config-section .isWide=${this.isWide}>
@ -206,10 +214,11 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
</div>
`
: ""}
</hass-subpage>
</hass-tabs-subpage>
<ha-fab
?is-wide=${this.isWide}
?narrow=${this.narrow}
icon="hass:plus"
title="${hass.localize("ui.panel.config.zone.add_zone")}"
@click=${this._createZone}
@ -389,6 +398,10 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
static get styles(): CSSResult {
return css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
a {
color: var(--primary-color);
}
@ -449,6 +462,9 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
}
`;
}
}