diff --git a/demo/public/assets/teachingbirds/plants.png b/demo/public/assets/teachingbirds/plants.png
new file mode 100644
index 0000000000..341b41b880
Binary files /dev/null and b/demo/public/assets/teachingbirds/plants.png differ
diff --git a/demo/public/index.html b/demo/public/index.html
index c38b7ce1f8..fae365931d 100644
--- a/demo/public/index.html
+++ b/demo/public/index.html
@@ -7,6 +7,32 @@
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Home Assistant Demo
diff --git a/demo/src/configs/demo-configs.ts b/demo/src/configs/demo-configs.ts
index 143c237085..4f0ee519f4 100644
--- a/demo/src/configs/demo-configs.ts
+++ b/demo/src/configs/demo-configs.ts
@@ -18,9 +18,12 @@ export const setDemoConfig = async (
lovelace: Lovelace,
index: number
) => {
+ const confProm = demoConfigs[index]();
+ const config = await confProm;
+
selectedDemoConfigIndex = index;
- selectedDemoConfig = demoConfigs[index]();
- const config = await selectedDemoConfig;
+ selectedDemoConfig = confProm;
+
hass.addEntities(config.entities(), true);
lovelace.saveConfig(config.lovelace());
hass.mockTheme(config.theme());
diff --git a/demo/src/configs/kernehed/lovelace.ts b/demo/src/configs/kernehed/lovelace.ts
index a8b108c94e..e9c87e1966 100644
--- a/demo/src/configs/kernehed/lovelace.ts
+++ b/demo/src/configs/kernehed/lovelace.ts
@@ -352,9 +352,18 @@ export const demoLovelaceKernehed: () => LovelaceConfig = () => ({
},
{
entities: [
- "sensor.pi_hole_dns_queries_today",
- "sensor.pi_hole_ads_blocked_today",
- "sensor.pi_hole_dns_unique_clients",
+ {
+ entity: "sensor.pi_hole_dns_queries_today",
+ 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,
type: "entities",
@@ -369,16 +378,16 @@ export const demoLovelaceKernehed: () => LovelaceConfig = () => ({
"binary_sensor.windows_server",
"binary_sensor.teamspeak",
"binary_sensor.harmony_hub",
- {
- style: {
- height: "1px",
- width: "85%",
- "margin-left": "auto",
- background: "#62717b",
- "margin-right": "auto",
- },
- type: "divider",
- },
+ // {
+ // style: {
+ // height: "1px",
+ // width: "85%",
+ // "margin-left": "auto",
+ // background: "#62717b",
+ // "margin-right": "auto",
+ // },
+ // type: "divider",
+ // },
// {
// items: ["sensor.uptime_router", "sensor.installerad_routeros"],
// head: {
diff --git a/demo/src/configs/teachingbirds/entities.ts b/demo/src/configs/teachingbirds/entities.ts
index 416ff322c5..029c19f2d3 100644
--- a/demo/src/configs/teachingbirds/entities.ts
+++ b/demo/src/configs/teachingbirds/entities.ts
@@ -1239,7 +1239,7 @@ export const demoEntitiesTeachingbirds: () => Entity[] = () =>
},
"sensor.herbs_moisture": {
entity_id: "sensor.herbs_moisture",
- state: "unknown",
+ state: "39",
attributes: {
unit_of_measurement: "%",
friendly_name: "Herbs moisture",
@@ -1448,7 +1448,7 @@ export const demoEntitiesTeachingbirds: () => Entity[] = () =>
},
"sensor.big_chili_moisture": {
entity_id: "sensor.big_chili_moisture",
- state: "0",
+ state: "36",
attributes: {
unit_of_measurement: "%",
friendly_name: "Big chili moisture",
@@ -1497,7 +1497,7 @@ export const demoEntitiesTeachingbirds: () => Entity[] = () =>
},
"sensor.small_chili_moisture": {
entity_id: "sensor.small_chili_moisture",
- state: "unknown",
+ state: "34",
attributes: {
unit_of_measurement: "%",
friendly_name: "Small chili moisture",
diff --git a/demo/src/configs/teachingbirds/lovelace.ts b/demo/src/configs/teachingbirds/lovelace.ts
index c28ada87d2..c6c585a185 100644
--- a/demo/src/configs/teachingbirds/lovelace.ts
+++ b/demo/src/configs/teachingbirds/lovelace.ts
@@ -231,62 +231,62 @@ export const demoLovelaceTeachingbirds: () => LovelaceConfig = () => ({
cards: [
{
cards: [
- {
- entities: [
- {
- name: "Front door lock",
- entity: "sensor.front_door_lock",
- },
- {
- name: "Yard door lock",
- entity: "sensor.yard_door_lock",
- },
- "sensor.front_door",
- "sensor.back_door",
- "sensor.backyard_door",
- "sensor.balcony_door",
- "sensor.yard_door",
- {
- name: "Dining area",
- entity: "sensor.dining_area_window",
- },
- {
- name: "Bedroom",
- entity: "sensor.bedroom_window",
- },
- {
- name: "Ring motion",
- entity: "sensor.front_door_outdoor_movement",
- },
- "sensor.hallway_movement",
- "sensor.passage_movement",
- "sensor.upstairs_hallway_movement",
- "sensor.living_room_movement",
- "sensor.back_door_camera_movement",
- {
- name: "Storage door",
- entity: "sensor.yard_storage_door",
- },
- "sensor.water_heater",
- "sensor.kitchen_sink",
- "binary_sensor.smoke_sensor_158d0001d37bdd",
- "binary_sensor.smoke_sensor_158d0001d37be5",
- "binary_sensor.smoke_sensor_158d0001d37c82",
- ],
- show_empty: false,
- type: "entity-filter",
- card: {
- type: "glance",
- show_state: false,
- },
- state_filter: [
- "Open",
- "Movement detected",
- "Leaking",
- "Unlocked",
- "on",
- ],
- },
+ // {
+ // entities: [
+ // {
+ // name: "Front door lock",
+ // entity: "sensor.front_door_lock",
+ // },
+ // {
+ // name: "Yard door lock",
+ // entity: "sensor.yard_door_lock",
+ // },
+ // "sensor.front_door",
+ // "sensor.back_door",
+ // "sensor.backyard_door",
+ // "sensor.balcony_door",
+ // "sensor.yard_door",
+ // {
+ // name: "Dining area",
+ // entity: "sensor.dining_area_window",
+ // },
+ // {
+ // name: "Bedroom",
+ // entity: "sensor.bedroom_window",
+ // },
+ // {
+ // name: "Ring motion",
+ // entity: "sensor.front_door_outdoor_movement",
+ // },
+ // "sensor.hallway_movement",
+ // "sensor.passage_movement",
+ // "sensor.upstairs_hallway_movement",
+ // "sensor.living_room_movement",
+ // "sensor.back_door_camera_movement",
+ // {
+ // name: "Storage door",
+ // entity: "sensor.yard_storage_door",
+ // },
+ // "sensor.water_heater",
+ // "sensor.kitchen_sink",
+ // "binary_sensor.smoke_sensor_158d0001d37bdd",
+ // "binary_sensor.smoke_sensor_158d0001d37be5",
+ // "binary_sensor.smoke_sensor_158d0001d37c82",
+ // ],
+ // show_empty: false,
+ // type: "entity-filter",
+ // card: {
+ // type: "glance",
+ // show_state: false,
+ // },
+ // state_filter: [
+ // "Open",
+ // "Movement detected",
+ // "Leaking",
+ // "Unlocked",
+ // "on",
+ // ],
+ // },
{
entities: [
"light.outdoor_lights",
@@ -384,6 +384,48 @@ export const demoLovelaceTeachingbirds: () => LovelaceConfig = () => ({
},
{
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,
// entity: "camera.stockholm_meteogram",
@@ -676,6 +718,7 @@ export const demoLovelaceTeachingbirds: () => LovelaceConfig = () => ({
},
],
title: "Home info",
+ path: "home_info",
icon: "mdi:home-heart",
},
{
diff --git a/demo/src/configs/teachingbirds/theme.ts b/demo/src/configs/teachingbirds/theme.ts
index 2bcc194c96..890100d90e 100644
--- a/demo/src/configs/teachingbirds/theme.ts
+++ b/demo/src/configs/teachingbirds/theme.ts
@@ -6,6 +6,7 @@ export const demoThemeTeachingbirds = () => ({
"paper-item-icon-color": "#d3d3d3",
"divider-color": "rgba(255, 255, 255, 0.12)",
"primary-color": "#389638",
+ "light-primary-color": "#6f956f",
"label-badge-red": "var(--primary-color)",
"paper-slider-secondary-color": "var(--light-primary-color)",
"paper-slider-knob-color": "var(--primary-color)",
diff --git a/demo/src/custom-cards/ha-demo-card.ts b/demo/src/custom-cards/ha-demo-card.ts
index 0ea3ee5d12..0555c42bcb 100644
--- a/demo/src/custom-cards/ha-demo-card.ts
+++ b/demo/src/custom-cards/ha-demo-card.ts
@@ -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 "@polymer/paper-icon-button";
+import "@polymer/paper-button";
+import "@polymer/paper-spinner/paper-spinner-lite";
import "../../../src/components/ha-card";
import { LovelaceCard, Lovelace } from "../../../src/panels/lovelace/types";
import { LovelaceCardConfig } from "../../../src/data/lovelace";
@@ -15,6 +23,15 @@ import {
export class HADemoCard extends LitElement implements LovelaceCard {
public lovelace?: Lovelace;
public hass?: MockHomeAssistant;
+ private _switching?: boolean;
+
+ static get properties(): PropertyDeclarations {
+ return {
+ lovelace: {},
+ hass: {},
+ _switching: {},
+ };
+ }
public getCardSize() {
return 2;
@@ -28,36 +45,51 @@ export class HADemoCard extends LitElement implements LovelaceCard {
protected render() {
return html`
-
+
${
- until(
- selectedDemoConfig.then(
- (conf) => html`
- ${conf.name}
-
- by
-
- ${conf.authorName}
-
-
+ this._switching
+ ? html`
+
`
- ),
- ""
- )
+ : until(
+ selectedDemoConfig.then(
+ (conf) => html`
+ ${conf.name}
+
+ by
+
+ ${conf.authorName}
+
+
+ `
+ ),
+ ""
+ )
}
+
+ Welcome home! You've reached the Home Assistant demo where we showcase
+ the best UIs created by our community.
+
+
`;
}
@@ -78,39 +110,28 @@ export class HADemoCard extends LitElement implements LovelaceCard {
);
}
- private _updateConfig(index: number) {
- setDemoConfig(this.hass!, this.lovelace!, index);
+ private async _updateConfig(index: number) {
+ 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[] {
return [
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 {
color: var(--primary-color);
}
+ .content {
+ padding: 16px;
+ }
+
.picker {
display: flex;
justify-content: space-between;
@@ -125,6 +146,15 @@ export class HADemoCard extends LitElement implements LovelaceCard {
.picker small {
display: block;
}
+
+ .actions {
+ padding-left: 5px;
+ }
+
+ .actions paper-button {
+ color: var(--primary-color);
+ font-weight: 500;
+ }
`,
];
}
diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts
index e1992c95e3..71174d0ada 100644
--- a/demo/src/ha-demo.ts
+++ b/demo/src/ha-demo.ts
@@ -12,6 +12,10 @@ class HaDemo extends HomeAssistant {
protected async _handleConnProm() {
const initial: Partial = {
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);
diff --git a/setup.py b/setup.py
index 529e9283c1..994142f72e 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
- version="20190120.0",
+ version="20190121.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts
index f1fb42b133..4bb724ee01 100644
--- a/src/fake_data/provide_hass.ts
+++ b/src/fake_data/provide_hass.ts
@@ -45,15 +45,8 @@ export const provideHass = (
} = {};
const entities = {};
- function updateHass(obj: Partial) {
- const newHass = { ...hass(), ...obj };
- elements.forEach((el) => {
- el.hass = newHass;
- });
- }
-
function updateStates(newStates: HassEntities) {
- updateHass({
+ hass().updateHass({
states: { ...hass().states, ...newStates },
});
}
@@ -66,7 +59,7 @@ export const provideHass = (
states[ent.entityId] = ent.toState();
});
if (replace) {
- updateHass({
+ hass().updateHass({
states,
});
} else {
@@ -93,7 +86,7 @@ export const provideHass = (
);
});
- updateHass({
+ const hassObj: MockHomeAssistant = {
// Home Assistant properties
auth: {} as any,
connection: {
@@ -141,11 +134,11 @@ export const provideHass = (
panelUrl: "lovelace",
language: getActiveTranslation(),
- resources: null,
+ resources: null as any,
- translationMetadata,
+ translationMetadata: translationMetadata as any,
dockedSidebar: false,
- moreInfoEntityId: null,
+ moreInfoEntityId: null as any,
async callService(domain, service, data) {
fireEvent(elements[0], "hass-notification", {
message: `Called service ${domain}/${service}`,
@@ -197,7 +190,12 @@ export const provideHass = (
// Mock stuff
mockEntities: entities,
- updateHass,
+ updateHass(obj: Partial) {
+ const newHass = { ...hass(), ...obj };
+ elements.forEach((el) => {
+ el.hass = newHass;
+ });
+ },
updateStates,
addEntities,
mockWS(type, callback) {
@@ -208,7 +206,7 @@ export const provideHass = (
(eventListeners[event] || []).forEach((fn) => fn(event));
},
mockTheme(theme) {
- updateHass({
+ hass().updateHass({
selectedTheme: theme ? "mock" : "default",
themes: {
...hass().themes,
@@ -217,15 +215,22 @@ export const provideHass = (
},
},
});
- const hassObj = hass();
- elements.forEach((el) => {
- applyThemesOnElement(el, hassObj.themes, hassObj.selectedTheme, true);
- });
+ const { themes, selectedTheme } = hass();
+ applyThemesOnElement(
+ document.documentElement,
+ themes,
+ selectedTheme,
+ true
+ );
},
...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
- return hass();
+ return hassObj;
};
diff --git a/src/layouts/app/home-assistant.js b/src/layouts/app/home-assistant.js
index 37426f25da..4201d70656 100644
--- a/src/layouts/app/home-assistant.js
+++ b/src/layouts/app/home-assistant.js
@@ -47,14 +47,16 @@ export class HomeAssistant extends ext(PolymerElement, [
use-hash-as-path="[[_useHashAsPath]]"
>
diff --git a/src/layouts/home-assistant-main.js b/src/layouts/home-assistant-main.js
index fe1b04fe92..6e4379b603 100644
--- a/src/layouts/home-assistant-main.js
+++ b/src/layouts/home-assistant-main.js
@@ -81,6 +81,9 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
return {
hass: Object,
narrow: Boolean,
+ tail: {
+ type: Object,
+ },
route: {
type: Object,
observer: "_routeChanged",
@@ -135,7 +138,7 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
connectedCallback() {
super.connectedCallback();
- if (this.route.prefix === "") {
+ if (this.tail.prefix === "") {
this.navigate(`/${localStorage.defaultPage || DEFAULT_PANEL}`, true);
}
}
diff --git a/src/layouts/partial-panel-resolver.js b/src/layouts/partial-panel-resolver.js
deleted file mode 100644
index fd7fbf3ff0..0000000000
--- a/src/layouts/partial-panel-resolver.js
+++ /dev/null
@@ -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`
-
-
-
-
-
-
-
-
-
-
-
- `;
- }
-
- 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);
diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts
new file mode 100644
index 0000000000..90b3ccb01f
--- /dev/null
+++ b/src/layouts/partial-panel-resolver.ts
@@ -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 } = {};
+
+// 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 | 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`
+
+ `;
+ }
+
+ if (!this._panelEl) {
+ return html`
+
+ `;
+ }
+
+ 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);
diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts
index 98ca2f1abd..4864fa20e5 100644
--- a/src/panels/lovelace/ha-panel-lovelace.ts
+++ b/src/panels/lovelace/ha-panel-lovelace.ts
@@ -4,7 +4,7 @@ import { fetchConfig, LovelaceConfig, saveConfig } from "../../data/lovelace";
import "../../layouts/hass-loading-screen";
import "../../layouts/hass-error-screen";
import "./hui-root";
-import { HomeAssistant, PanelInfo } from "../../types";
+import { HomeAssistant, PanelInfo, Route } from "../../types";
import { Lovelace } from "./types";
import { LitElement, html, PropertyValues, TemplateResult } from "lit-element";
import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin";
@@ -22,7 +22,7 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
public hass?: HomeAssistant;
public narrow?: boolean;
public showMenu?: boolean;
- public route?: object;
+ public route?: Route;
private _columns?: number;
private _state?: "loading" | "loaded" | "error" | "yaml-editor";
private _errorMsg?: string;
diff --git a/src/types.ts b/src/types.ts
index 7b7a174f47..532f539825 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -86,7 +86,7 @@ export interface HomeAssistant {
services: HassServices;
config: HassConfig;
themes: Themes;
- selectedTheme: string | null;
+ selectedTheme?: string | null;
panels: Panels;
panelUrl: string;
language: string;
@@ -169,3 +169,16 @@ export interface PanelInfo {
url_path: string;
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;
+}
diff --git a/translations/uk.json b/translations/uk.json
index ee9e36de6f..f0043e13ef 100644
--- a/translations/uk.json
+++ b/translations/uk.json
@@ -393,6 +393,12 @@
},
"state": {
"for": "Протягом"
+ },
+ "time_pattern": {
+ "label": "Шаблон часу",
+ "hours": "Годин",
+ "minutes": "Хвилин",
+ "seconds": "Секунд"
}
}
},
@@ -832,6 +838,7 @@
},
"sun": {
"elevation": "Висота",
+ "rising": "Схід сонця",
"setting": "Налаштування"
},
"updater": {