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": {