Merge pull request #2527 from home-assistant/dev

20190121.0
This commit is contained in:
Paulus Schoutsen 2019-01-21 09:19:12 -08:00 committed by GitHub
commit 16c9303ae9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 555 additions and 387 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -7,6 +7,32 @@
content="width=device-width, initial-scale=1, shrink-to-fit=no" content="width=device-width, initial-scale=1, shrink-to-fit=no"
/> />
<meta name="theme-color" content="#2157BC" /> <meta name="theme-color" content="#2157BC" />
<meta property="fb:app_id" content="338291289691179" />
<meta property="og:title" content="Home Assistant Demo" />
<meta property="og:site_name" content="Home Assistant" />
<meta property="og:url" content="https://demo.home-assistant.io/" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
property="og:image"
content="https://www.home-assistant.io/images/default-social.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@home_assistant" />
<meta name="twitter:title" content="Home Assistant" />
<meta
name="twitter:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
name="twitter:image"
content="https://www.home-assistant.io/images/default-social.png"
/>
<title>Home Assistant Demo</title> <title>Home Assistant Demo</title>
<script src="./custom-elements-es5-adapter.js"></script> <script src="./custom-elements-es5-adapter.js"></script>
<script src="./compatibility.js"></script> <script src="./compatibility.js"></script>

View File

@ -18,9 +18,12 @@ export const setDemoConfig = async (
lovelace: Lovelace, lovelace: Lovelace,
index: number index: number
) => { ) => {
const confProm = demoConfigs[index]();
const config = await confProm;
selectedDemoConfigIndex = index; selectedDemoConfigIndex = index;
selectedDemoConfig = demoConfigs[index](); selectedDemoConfig = confProm;
const config = await selectedDemoConfig;
hass.addEntities(config.entities(), true); hass.addEntities(config.entities(), true);
lovelace.saveConfig(config.lovelace()); lovelace.saveConfig(config.lovelace());
hass.mockTheme(config.theme()); hass.mockTheme(config.theme());

View File

@ -352,9 +352,18 @@ export const demoLovelaceKernehed: () => LovelaceConfig = () => ({
}, },
{ {
entities: [ entities: [
"sensor.pi_hole_dns_queries_today", {
"sensor.pi_hole_ads_blocked_today", entity: "sensor.pi_hole_dns_queries_today",
"sensor.pi_hole_dns_unique_clients", name: "DNS Queries Today",
},
{
entity: "sensor.pi_hole_ads_blocked_today",
name: "Ads Blocked Today",
},
{
entity: "sensor.pi_hole_dns_unique_clients",
name: "DNS Unique Clients",
},
], ],
show_header_toggle: false, show_header_toggle: false,
type: "entities", type: "entities",
@ -369,16 +378,16 @@ export const demoLovelaceKernehed: () => LovelaceConfig = () => ({
"binary_sensor.windows_server", "binary_sensor.windows_server",
"binary_sensor.teamspeak", "binary_sensor.teamspeak",
"binary_sensor.harmony_hub", "binary_sensor.harmony_hub",
{ // {
style: { // style: {
height: "1px", // height: "1px",
width: "85%", // width: "85%",
"margin-left": "auto", // "margin-left": "auto",
background: "#62717b", // background: "#62717b",
"margin-right": "auto", // "margin-right": "auto",
}, // },
type: "divider", // type: "divider",
}, // },
// { // {
// items: ["sensor.uptime_router", "sensor.installerad_routeros"], // items: ["sensor.uptime_router", "sensor.installerad_routeros"],
// head: { // head: {

View File

@ -1239,7 +1239,7 @@ export const demoEntitiesTeachingbirds: () => Entity[] = () =>
}, },
"sensor.herbs_moisture": { "sensor.herbs_moisture": {
entity_id: "sensor.herbs_moisture", entity_id: "sensor.herbs_moisture",
state: "unknown", state: "39",
attributes: { attributes: {
unit_of_measurement: "%", unit_of_measurement: "%",
friendly_name: "Herbs moisture", friendly_name: "Herbs moisture",
@ -1448,7 +1448,7 @@ export const demoEntitiesTeachingbirds: () => Entity[] = () =>
}, },
"sensor.big_chili_moisture": { "sensor.big_chili_moisture": {
entity_id: "sensor.big_chili_moisture", entity_id: "sensor.big_chili_moisture",
state: "0", state: "36",
attributes: { attributes: {
unit_of_measurement: "%", unit_of_measurement: "%",
friendly_name: "Big chili moisture", friendly_name: "Big chili moisture",
@ -1497,7 +1497,7 @@ export const demoEntitiesTeachingbirds: () => Entity[] = () =>
}, },
"sensor.small_chili_moisture": { "sensor.small_chili_moisture": {
entity_id: "sensor.small_chili_moisture", entity_id: "sensor.small_chili_moisture",
state: "unknown", state: "34",
attributes: { attributes: {
unit_of_measurement: "%", unit_of_measurement: "%",
friendly_name: "Small chili moisture", friendly_name: "Small chili moisture",

View File

@ -231,62 +231,62 @@ export const demoLovelaceTeachingbirds: () => LovelaceConfig = () => ({
cards: [ cards: [
{ {
cards: [ cards: [
{ // {
entities: [ // entities: [
{ // {
name: "Front door lock", // name: "Front door lock",
entity: "sensor.front_door_lock", // entity: "sensor.front_door_lock",
}, // },
{ // {
name: "Yard door lock", // name: "Yard door lock",
entity: "sensor.yard_door_lock", // entity: "sensor.yard_door_lock",
}, // },
"sensor.front_door", // "sensor.front_door",
"sensor.back_door", // "sensor.back_door",
"sensor.backyard_door", // "sensor.backyard_door",
"sensor.balcony_door", // "sensor.balcony_door",
"sensor.yard_door", // "sensor.yard_door",
{ // {
name: "Dining area", // name: "Dining area",
entity: "sensor.dining_area_window", // entity: "sensor.dining_area_window",
}, // },
{ // {
name: "Bedroom", // name: "Bedroom",
entity: "sensor.bedroom_window", // entity: "sensor.bedroom_window",
}, // },
{ // {
name: "Ring motion", // name: "Ring motion",
entity: "sensor.front_door_outdoor_movement", // entity: "sensor.front_door_outdoor_movement",
}, // },
"sensor.hallway_movement", // "sensor.hallway_movement",
"sensor.passage_movement", // "sensor.passage_movement",
"sensor.upstairs_hallway_movement", // "sensor.upstairs_hallway_movement",
"sensor.living_room_movement", // "sensor.living_room_movement",
"sensor.back_door_camera_movement", // "sensor.back_door_camera_movement",
{ // {
name: "Storage door", // name: "Storage door",
entity: "sensor.yard_storage_door", // entity: "sensor.yard_storage_door",
}, // },
"sensor.water_heater", // "sensor.water_heater",
"sensor.kitchen_sink", // "sensor.kitchen_sink",
"binary_sensor.smoke_sensor_158d0001d37bdd", // "binary_sensor.smoke_sensor_158d0001d37bdd",
"binary_sensor.smoke_sensor_158d0001d37be5", // "binary_sensor.smoke_sensor_158d0001d37be5",
"binary_sensor.smoke_sensor_158d0001d37c82", // "binary_sensor.smoke_sensor_158d0001d37c82",
], // ],
show_empty: false, // show_empty: false,
type: "entity-filter", // type: "entity-filter",
card: { // card: {
type: "glance", // type: "glance",
show_state: false, // show_state: false,
}, // },
state_filter: [ // state_filter: [
"Open", // "Open",
"Movement detected", // "Movement detected",
"Leaking", // "Leaking",
"Unlocked", // "Unlocked",
"on", // "on",
], // ],
}, // },
{ {
entities: [ entities: [
"light.outdoor_lights", "light.outdoor_lights",
@ -384,6 +384,48 @@ export const demoLovelaceTeachingbirds: () => LovelaceConfig = () => ({
}, },
{ {
cards: [ cards: [
{
image: "/assets/teachingbirds/plants.png",
elements: [
{
style: {
top: "30%",
"--ha-label-badge-font-size": "1em",
left: "10%",
},
type: "state-badge",
entity: "sensor.small_chili_moisture",
},
{
style: {
top: "30%",
"--ha-label-badge-font-size": "1em",
left: "25%",
},
type: "state-badge",
entity: "sensor.big_chili_moisture",
},
{
style: {
top: "30%",
"--ha-label-badge-font-size": "1em",
left: "40%",
},
type: "state-badge",
entity: "sensor.herbs_moisture",
},
{
style: {
top: "12%",
"--ha-label-badge-font-size": "1em",
left: "92%",
},
type: "state-label",
entity: "sensor.greenhouse_temperature",
},
],
type: "picture-elements",
},
{ {
// show_name: false, // show_name: false,
// entity: "camera.stockholm_meteogram", // entity: "camera.stockholm_meteogram",
@ -676,6 +718,7 @@ export const demoLovelaceTeachingbirds: () => LovelaceConfig = () => ({
}, },
], ],
title: "Home info", title: "Home info",
path: "home_info",
icon: "mdi:home-heart", icon: "mdi:home-heart",
}, },
{ {

View File

@ -6,6 +6,7 @@ export const demoThemeTeachingbirds = () => ({
"paper-item-icon-color": "#d3d3d3", "paper-item-icon-color": "#d3d3d3",
"divider-color": "rgba(255, 255, 255, 0.12)", "divider-color": "rgba(255, 255, 255, 0.12)",
"primary-color": "#389638", "primary-color": "#389638",
"light-primary-color": "#6f956f",
"label-badge-red": "var(--primary-color)", "label-badge-red": "var(--primary-color)",
"paper-slider-secondary-color": "var(--light-primary-color)", "paper-slider-secondary-color": "var(--light-primary-color)",
"paper-slider-knob-color": "var(--primary-color)", "paper-slider-knob-color": "var(--primary-color)",

View File

@ -1,6 +1,14 @@
import { LitElement, html, CSSResult, css } from "lit-element"; import {
LitElement,
html,
CSSResult,
css,
PropertyDeclarations,
} from "lit-element";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import "@polymer/paper-icon-button"; import "@polymer/paper-icon-button";
import "@polymer/paper-button";
import "@polymer/paper-spinner/paper-spinner-lite";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { LovelaceCard, Lovelace } from "../../../src/panels/lovelace/types"; import { LovelaceCard, Lovelace } from "../../../src/panels/lovelace/types";
import { LovelaceCardConfig } from "../../../src/data/lovelace"; import { LovelaceCardConfig } from "../../../src/data/lovelace";
@ -15,6 +23,15 @@ import {
export class HADemoCard extends LitElement implements LovelaceCard { export class HADemoCard extends LitElement implements LovelaceCard {
public lovelace?: Lovelace; public lovelace?: Lovelace;
public hass?: MockHomeAssistant; public hass?: MockHomeAssistant;
private _switching?: boolean;
static get properties(): PropertyDeclarations {
return {
lovelace: {},
hass: {},
_switching: {},
};
}
public getCardSize() { public getCardSize() {
return 2; return 2;
@ -28,36 +45,51 @@ export class HADemoCard extends LitElement implements LovelaceCard {
protected render() { protected render() {
return html` return html`
<ha-card header="Home Assistant Demo Switcher"> <ha-card>
<div class="picker"> <div class="picker">
<paper-icon-button <paper-icon-button
@click=${this._prevConfig} @click=${this._prevConfig}
icon="hass:chevron-right" icon="hass:chevron-right"
style="transform: rotate(180deg)" style="transform: rotate(180deg)"
.disabled=${this._switching}
></paper-icon-button> ></paper-icon-button>
<div> <div>
${ ${
until( this._switching
selectedDemoConfig.then( ? html`
(conf) => html` <paper-spinner-lite active></paper-spinner-lite>
${conf.name}
<small>
by
<a target="_blank" href="${conf.authorUrl}">
${conf.authorName}
</a>
</small>
` `
), : until(
"" selectedDemoConfig.then(
) (conf) => html`
${conf.name}
<small>
by
<a target="_blank" href="${conf.authorUrl}">
${conf.authorName}
</a>
</small>
`
),
""
)
} }
</div> </div>
<paper-icon-button <paper-icon-button
@click=${this._nextConfig} @click=${this._nextConfig}
icon="hass:chevron-right" icon="hass:chevron-right"
.disabled=${this._switching}
></paper-icon-button> ></paper-icon-button>
</div> </div>
<div class="content">
Welcome home! You've reached the Home Assistant demo where we showcase
the best UIs created by our community.
</div>
<div class="actions">
<a href="https://www.home-assistant.io" target="_blank">
<paper-button>Learn more about Home Assistant</paper-button>
</a>
</div>
</ha-card> </ha-card>
`; `;
} }
@ -78,39 +110,28 @@ export class HADemoCard extends LitElement implements LovelaceCard {
); );
} }
private _updateConfig(index: number) { private async _updateConfig(index: number) {
setDemoConfig(this.hass!, this.lovelace!, index); this._switching = true;
try {
await setDemoConfig(this.hass!, this.lovelace!, index);
} catch (err) {
alert("Failed to switch config :-(");
} finally {
this._switching = false;
}
} }
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
css` css`
.content {
padding: 0 16px;
}
ul {
margin-top: 0;
margin-bottom: 0;
padding: 16px 16px 16px 38px;
}
li {
padding: 8px 0;
}
li:first-child {
margin-top: -8px;
}
li:last-child {
margin-bottom: -8px;
}
a { a {
color: var(--primary-color); color: var(--primary-color);
} }
.content {
padding: 16px;
}
.picker { .picker {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -125,6 +146,15 @@ export class HADemoCard extends LitElement implements LovelaceCard {
.picker small { .picker small {
display: block; display: block;
} }
.actions {
padding-left: 5px;
}
.actions paper-button {
color: var(--primary-color);
font-weight: 500;
}
`, `,
]; ];
} }

View File

@ -12,6 +12,10 @@ class HaDemo extends HomeAssistant {
protected async _handleConnProm() { protected async _handleConnProm() {
const initial: Partial<HomeAssistant> = { const initial: Partial<HomeAssistant> = {
panelUrl: (this as any).panelUrl, panelUrl: (this as any).panelUrl,
// Override updateHass so that the correct hass lifecycle methods are called
updateHass: (hassUpdate) =>
// @ts-ignore
this._updateHass(hassUpdate),
}; };
const hass = provideHass(this, initial); const hass = provideHass(this, initial);

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name="home-assistant-frontend", name="home-assistant-frontend",
version="20190120.0", version="20190121.0",
description="The Home Assistant frontend", description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer", url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors", author="The Home Assistant Authors",

View File

@ -45,15 +45,8 @@ export const provideHass = (
} = {}; } = {};
const entities = {}; const entities = {};
function updateHass(obj: Partial<MockHomeAssistant>) {
const newHass = { ...hass(), ...obj };
elements.forEach((el) => {
el.hass = newHass;
});
}
function updateStates(newStates: HassEntities) { function updateStates(newStates: HassEntities) {
updateHass({ hass().updateHass({
states: { ...hass().states, ...newStates }, states: { ...hass().states, ...newStates },
}); });
} }
@ -66,7 +59,7 @@ export const provideHass = (
states[ent.entityId] = ent.toState(); states[ent.entityId] = ent.toState();
}); });
if (replace) { if (replace) {
updateHass({ hass().updateHass({
states, states,
}); });
} else { } else {
@ -93,7 +86,7 @@ export const provideHass = (
); );
}); });
updateHass({ const hassObj: MockHomeAssistant = {
// Home Assistant properties // Home Assistant properties
auth: {} as any, auth: {} as any,
connection: { connection: {
@ -141,11 +134,11 @@ export const provideHass = (
panelUrl: "lovelace", panelUrl: "lovelace",
language: getActiveTranslation(), language: getActiveTranslation(),
resources: null, resources: null as any,
translationMetadata, translationMetadata: translationMetadata as any,
dockedSidebar: false, dockedSidebar: false,
moreInfoEntityId: null, moreInfoEntityId: null as any,
async callService(domain, service, data) { async callService(domain, service, data) {
fireEvent(elements[0], "hass-notification", { fireEvent(elements[0], "hass-notification", {
message: `Called service ${domain}/${service}`, message: `Called service ${domain}/${service}`,
@ -197,7 +190,12 @@ export const provideHass = (
// Mock stuff // Mock stuff
mockEntities: entities, mockEntities: entities,
updateHass, updateHass(obj: Partial<MockHomeAssistant>) {
const newHass = { ...hass(), ...obj };
elements.forEach((el) => {
el.hass = newHass;
});
},
updateStates, updateStates,
addEntities, addEntities,
mockWS(type, callback) { mockWS(type, callback) {
@ -208,7 +206,7 @@ export const provideHass = (
(eventListeners[event] || []).forEach((fn) => fn(event)); (eventListeners[event] || []).forEach((fn) => fn(event));
}, },
mockTheme(theme) { mockTheme(theme) {
updateHass({ hass().updateHass({
selectedTheme: theme ? "mock" : "default", selectedTheme: theme ? "mock" : "default",
themes: { themes: {
...hass().themes, ...hass().themes,
@ -217,15 +215,22 @@ export const provideHass = (
}, },
}, },
}); });
const hassObj = hass(); const { themes, selectedTheme } = hass();
elements.forEach((el) => { applyThemesOnElement(
applyThemesOnElement(el, hassObj.themes, hassObj.selectedTheme, true); document.documentElement,
}); themes,
selectedTheme,
true
);
}, },
...overrideData, ...overrideData,
} as MockHomeAssistant); };
// Update the elements. Note, we call it on hassObj so that if it was
// overridden (like in the demo), it will still work.
hassObj.updateHass(hassObj);
// @ts-ignore // @ts-ignore
return hass(); return hassObj;
}; };

View File

@ -47,14 +47,16 @@ export class HomeAssistant extends ext(PolymerElement, [
use-hash-as-path="[[_useHashAsPath]]" use-hash-as-path="[[_useHashAsPath]]"
></app-location> ></app-location>
<app-route <app-route
route="{{route}}" route="[[route]]"
pattern="/:panel" pattern="/:panel"
data="{{routeData}}" data="{{routeData}}"
tail="{{subroute}}"
></app-route> ></app-route>
<template is="dom-if" if="[[showMain]]" restamp> <template is="dom-if" if="[[showMain]]" restamp>
<home-assistant-main <home-assistant-main
hass="[[hass]]" hass="[[hass]]"
route="{{route}}" route="[[route]]"
tail="[[subroute]]"
></home-assistant-main> ></home-assistant-main>
</template> </template>

View File

@ -81,6 +81,9 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
return { return {
hass: Object, hass: Object,
narrow: Boolean, narrow: Boolean,
tail: {
type: Object,
},
route: { route: {
type: Object, type: Object,
observer: "_routeChanged", observer: "_routeChanged",
@ -135,7 +138,7 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
if (this.route.prefix === "") { if (this.tail.prefix === "") {
this.navigate(`/${localStorage.defaultPage || DEFAULT_PANEL}`, true); this.navigate(`/${localStorage.defaultPage || DEFAULT_PANEL}`, true);
} }
} }

View File

@ -1,246 +0,0 @@
import "@polymer/app-route/app-route";
import { dom } from "@polymer/polymer/lib/legacy/polymer.dom";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "./hass-loading-screen";
import "./hass-error-screen";
import { importHref } from "../resources/html-import/import-href";
import dynamicContentUpdater from "../common/dom/dynamic_content_updater";
import NavigateMixin from "../mixins/navigate-mixin";
const loaded = {};
function ensureLoaded(panel) {
if (panel in loaded) return loaded[panel];
let imported;
// Name each panel we support here, that way Webpack knows about it.
switch (panel) {
case "config":
imported = import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config");
break;
case "custom":
imported = import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom");
break;
case "dev-event":
imported = import(/* webpackChunkName: "panel-dev-event" */ "../panels/dev-event/ha-panel-dev-event");
break;
case "dev-info":
imported = import(/* webpackChunkName: "panel-dev-info" */ "../panels/dev-info/ha-panel-dev-info");
break;
case "dev-mqtt":
imported = import(/* webpackChunkName: "panel-dev-mqtt" */ "../panels/dev-mqtt/ha-panel-dev-mqtt");
break;
case "dev-service":
imported = import(/* webpackChunkName: "panel-dev-service" */ "../panels/dev-service/ha-panel-dev-service");
break;
case "dev-state":
imported = import(/* webpackChunkName: "panel-dev-state" */ "../panels/dev-state/ha-panel-dev-state");
break;
case "dev-template":
imported = import(/* webpackChunkName: "panel-dev-template" */ "../panels/dev-template/ha-panel-dev-template");
break;
case "lovelace":
imported = import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace");
break;
case "states":
imported = import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states");
break;
case "history":
imported = import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history");
break;
case "iframe":
imported = import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe");
break;
case "kiosk":
imported = import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk");
break;
case "logbook":
imported = import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook");
break;
case "mailbox":
imported = import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox");
break;
case "map":
imported = import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map");
break;
case "profile":
imported = import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile");
break;
case "shopping-list":
imported = import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list");
break;
case "calendar":
imported = import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar");
break;
default:
imported = null;
}
if (imported != null) {
loaded[panel] = imported;
}
return imported;
}
/*
* @appliesMixin NavigateMixin
*/
class PartialPanelResolver extends NavigateMixin(PolymerElement) {
static get template() {
return html`
<style>
[hidden] {
display: none !important;
}
</style>
<app-route
route="{{route}}"
pattern="/:panel"
data="{{routeData}}"
tail="{{routeTail}}"
></app-route>
<template is="dom-if" if="[[_equal(_state, 'loading')]]">
<hass-loading-screen
narrow="[[narrow]]"
show-menu="[[showMenu]]"
></hass-loading-screen>
</template>
<template is="dom-if" if="[[_equal(_state, 'error')]]">
<hass-error-screen
title=""
error="Error while loading this panel."
narrow="[[narrow]]"
show-menu="[[showMenu]]"
></hass-error-screen>
</template>
<span id="panel" hidden$="[[!_equal(_state, 'loaded')]]"></span>
`;
}
static get properties() {
return {
hass: {
type: Object,
observer: "updateAttributes",
},
narrow: {
type: Boolean,
value: false,
observer: "updateAttributes",
},
showMenu: {
type: Boolean,
value: false,
observer: "updateAttributes",
},
route: Object,
routeData: Object,
routeTail: {
type: Object,
observer: "updateAttributes",
},
_state: {
type: String,
value: "loading",
},
panel: {
type: Object,
computed: "computeCurrentPanel(hass)",
observer: "panelChanged",
},
};
}
panelChanged(panel) {
if (!panel) {
if (this.$.panel.lastChild) {
this.$.panel.removeChild(this.$.panel.lastChild);
}
return;
}
this._state = "loading";
let loadingProm;
if (panel.url) {
loadingProm = new Promise((resolve, reject) =>
importHref(panel.url, resolve, reject)
);
} else {
loadingProm = ensureLoaded(panel.component_name);
}
if (loadingProm === null) {
this._state = "error";
return;
}
loadingProm.then(
() => {
dynamicContentUpdater(
this.$.panel,
"ha-panel-" + panel.component_name,
{
hass: this.hass,
narrow: this.narrow,
showMenu: this.showMenu,
route: this.routeTail,
panel: panel,
}
);
this._state = "loaded";
},
(err) => {
// eslint-disable-next-line
console.error("Error loading panel", err);
this._state = "error";
}
);
}
updateAttributes() {
var customEl = dom(this.$.panel).lastChild;
if (!customEl) return;
customEl.hass = this.hass;
customEl.narrow = this.narrow;
customEl.showMenu = this.showMenu;
customEl.route = this.routeTail;
}
computeCurrentPanel(hass) {
return hass.panels[hass.panelUrl];
}
_equal(a, b) {
return a === b;
}
}
customElements.define("partial-panel-resolver", PartialPanelResolver);

View File

@ -0,0 +1,268 @@
import {
LitElement,
html,
PropertyDeclarations,
PropertyValues,
} from "lit-element";
import "./hass-loading-screen";
import "./hass-error-screen";
import { HomeAssistant, Panel, PanelElement, Route } from "../types";
// Cache of panel loading promises.
const LOADED: { [panel: string]: Promise<void> } = {};
// Which panel elements we will cache.
// Maybe we can cache them all eventually, but not sure yet about
// unknown side effects (like history taking a lot of memory, reset needed)
const CACHED_EL = ["lovelace", "states"];
function ensureLoaded(panel): Promise<void> | null {
if (panel in LOADED) {
return LOADED[panel];
}
let imported;
// Name each panel we support here, that way Webpack knows about it.
switch (panel) {
case "config":
imported = import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config");
break;
case "custom":
imported = import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom");
break;
case "dev-event":
imported = import(/* webpackChunkName: "panel-dev-event" */ "../panels/dev-event/ha-panel-dev-event");
break;
case "dev-info":
imported = import(/* webpackChunkName: "panel-dev-info" */ "../panels/dev-info/ha-panel-dev-info");
break;
case "dev-mqtt":
imported = import(/* webpackChunkName: "panel-dev-mqtt" */ "../panels/dev-mqtt/ha-panel-dev-mqtt");
break;
case "dev-service":
imported = import(/* webpackChunkName: "panel-dev-service" */ "../panels/dev-service/ha-panel-dev-service");
break;
case "dev-state":
imported = import(/* webpackChunkName: "panel-dev-state" */ "../panels/dev-state/ha-panel-dev-state");
break;
case "dev-template":
imported = import(/* webpackChunkName: "panel-dev-template" */ "../panels/dev-template/ha-panel-dev-template");
break;
case "lovelace":
imported = import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace");
break;
case "states":
imported = import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states");
break;
case "history":
imported = import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history");
break;
case "iframe":
imported = import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe");
break;
case "kiosk":
imported = import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk");
break;
case "logbook":
imported = import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook");
break;
case "mailbox":
imported = import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox");
break;
case "map":
imported = import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map");
break;
case "profile":
imported = import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile");
break;
case "shopping-list":
imported = import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list");
break;
case "calendar":
imported = import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar");
break;
default:
imported = null;
}
if (imported != null) {
LOADED[panel] = imported;
}
return imported;
}
class PartialPanelResolver extends LitElement {
public hass?: HomeAssistant;
public narrow?: boolean;
public showMenu?: boolean;
public route?: Route | null;
private _routeTail?: Route | null;
private _panel?: Panel;
private _panelEl?: PanelElement;
private _error?: boolean;
private _cache: { [name: string]: PanelElement };
static get properties(): PropertyDeclarations {
return {
hass: {},
narrow: {},
showMenu: {},
route: {},
_routeTail: {},
_error: {},
_panelEl: {},
};
}
constructor() {
super();
this._cache = {};
}
protected render() {
if (this._error) {
return html`
<hass-error-screen
title=""
error="Error while loading this panel."
.narrow=${this.narrow}
.showMenu=${this.showMenu}
/>
`;
}
if (!this._panelEl) {
return html`
<hass-loading-screen
.narrow=${this.narrow}
.showMenu=${this.showMenu}
></hass-loading-screen>
`;
}
return html`
${this._panelEl}
`;
}
protected updated(changedProps: PropertyValues) {
if (!this.hass) {
return;
}
if (changedProps.has("route")) {
// Manual splitting
const route = this.route!;
const dividerPos = route.path.indexOf("/", 1);
this._routeTail = {
prefix: route.path.substr(0, dividerPos),
path: route.path.substr(dividerPos),
};
// If just route changed, no need to process further.
if (changedProps.size === 1) {
return;
}
}
if (changedProps.has("hass")) {
const panel = this.hass.panels[this.hass.panelUrl];
if (panel !== this._panel) {
this._panel = panel;
this._panelEl = undefined;
// Found cached one, use that
if (panel.component_name in this._cache) {
this._panelEl = this._cache[panel.component_name];
this._updatePanel();
return;
}
const loadingProm = ensureLoaded(panel.component_name);
if (loadingProm === null) {
this._error = true;
return;
}
loadingProm.then(
() => {
// If panel changed while loading.
if (this._panel !== panel) {
return;
}
this._panelEl = (this._panelEl = document.createElement(
`ha-panel-${panel.component_name}`
)) as PanelElement;
if (CACHED_EL.includes(panel.component_name)) {
this._cache[panel.component_name] = this._panelEl;
}
this._updatePanel();
},
(err) => {
// tslint:disable-next-line
console.error("Error loading panel", err);
this._error = true;
}
);
return;
}
}
this._updatePanel();
}
private _updatePanel() {
const el = this._panelEl;
if (!el) {
return;
}
if ("setProperties" in el) {
// As long as we have Polymer panels
(el as any).setProperties({
hass: this.hass,
narrow: this.narrow,
showMenu: this.showMenu,
route: this._routeTail,
panel: this._panel,
});
} else {
el.hass = this.hass;
el.narrow = this.narrow;
el.showMenu = this.showMenu;
el.route = this._routeTail;
el.panel = this._panel;
}
}
}
customElements.define("partial-panel-resolver", PartialPanelResolver);

View File

@ -4,7 +4,7 @@ import { fetchConfig, LovelaceConfig, saveConfig } from "../../data/lovelace";
import "../../layouts/hass-loading-screen"; import "../../layouts/hass-loading-screen";
import "../../layouts/hass-error-screen"; import "../../layouts/hass-error-screen";
import "./hui-root"; import "./hui-root";
import { HomeAssistant, PanelInfo } from "../../types"; import { HomeAssistant, PanelInfo, Route } from "../../types";
import { Lovelace } from "./types"; import { Lovelace } from "./types";
import { LitElement, html, PropertyValues, TemplateResult } from "lit-element"; import { LitElement, html, PropertyValues, TemplateResult } from "lit-element";
import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin";
@ -22,7 +22,7 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
public hass?: HomeAssistant; public hass?: HomeAssistant;
public narrow?: boolean; public narrow?: boolean;
public showMenu?: boolean; public showMenu?: boolean;
public route?: object; public route?: Route;
private _columns?: number; private _columns?: number;
private _state?: "loading" | "loaded" | "error" | "yaml-editor"; private _state?: "loading" | "loaded" | "error" | "yaml-editor";
private _errorMsg?: string; private _errorMsg?: string;

View File

@ -86,7 +86,7 @@ export interface HomeAssistant {
services: HassServices; services: HassServices;
config: HassConfig; config: HassConfig;
themes: Themes; themes: Themes;
selectedTheme: string | null; selectedTheme?: string | null;
panels: Panels; panels: Panels;
panelUrl: string; panelUrl: string;
language: string; language: string;
@ -169,3 +169,16 @@ export interface PanelInfo<T = unknown> {
url_path: string; url_path: string;
config: T; config: T;
} }
export interface Route {
prefix: string;
path: string;
}
export interface PanelElement extends HTMLElement {
hass?: HomeAssistant;
narrow?: boolean;
showMenu?: boolean;
route?: Route | null;
panel?: Panel;
}

View File

@ -393,6 +393,12 @@
}, },
"state": { "state": {
"for": "Протягом" "for": "Протягом"
},
"time_pattern": {
"label": "Шаблон часу",
"hours": "Годин",
"minutes": "Хвилин",
"seconds": "Секунд"
} }
} }
}, },
@ -832,6 +838,7 @@
}, },
"sun": { "sun": {
"elevation": "Висота", "elevation": "Висота",
"rising": "Схід сонця",
"setting": "Налаштування" "setting": "Налаштування"
}, },
"updater": { "updater": {