Merge pull request #2208 from home-assistant/dev

20181207.0
This commit is contained in:
Paulus Schoutsen 2018-12-07 07:10:32 +01:00 committed by GitHub
commit 181539baac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 373 additions and 221 deletions

View File

@ -172,10 +172,14 @@ const CONFIGS = [
- type: glance - type: glance
entities: entities:
- entity: lock.kitchen_door - entity: lock.kitchen_door
tap_action: toggle tap_action:
type: toggle
- entity: light.ceiling_lights - entity: light.ceiling_lights
tap_action: call-service tap_action:
action: call-service
service: light.turn_on service: light.turn_on
service_data:
entity_id: light.ceiling_lights
- device_tracker.demo_paulus - device_tracker.demo_paulus
- media_player.living_room - media_player.living_room
- sun.sun - sun.sun

View File

@ -56,7 +56,8 @@ const CONFIGS = [
--iron-icon-fill-color: rgba(50, 50, 50, .75) --iron-icon-fill-color: rgba(50, 50, 50, .75)
- type: image - type: image
entity: light.bed_light entity: light.bed_light
tap_action: toggle tap_action:
action: toggle
image: /images/light_bulb_off.png image: /images/light_bulb_off.png
state_image: state_image:
'on': /images/light_bulb_on.png 'on': /images/light_bulb_on.png

View File

@ -83,6 +83,23 @@ const CONFIGS = [
- binary_sensor.basement_floor_wet - binary_sensor.basement_floor_wet
`, `,
}, },
{
heading: "Custom tap action",
config: `
- type: picture-glance
image: /images/living_room.png
title: Living room
entity: light.ceiling_lights
tap_action:
action: toggle
entities:
- entity: switch.decorative_lights
icon: mdi:power
tap_action:
action: toggle
- binary_sensor.basement_floor_wet
`,
},
]; ];
class DemoPicGlance extends PolymerElement { class DemoPicGlance extends PolymerElement {

View File

@ -4,6 +4,8 @@
# Stop on errors # Stop on errors
set -e set -e
cd "$(dirname "$0")/.."
BUILD_DIR=build BUILD_DIR=build
OUTPUT_DIR=hass_frontend OUTPUT_DIR=hass_frontend
OUTPUT_DIR_ES5=hass_frontend_es5 OUTPUT_DIR_ES5=hass_frontend_es5

11
script/size_stats Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
# Analyze stats
# Stop on errors
set -e
cd "$(dirname "$0")/.."
STATS=1 NODE_ENV=production webpack --profile --json > compilation-stats.json
npx webpack-bundle-analyzer compilation-stats.json hass_frontend
rm compilation-stats.json

View File

@ -1,7 +1,9 @@
import Leaflet from "leaflet";
// Sets up a Leaflet map on the provided DOM element // Sets up a Leaflet map on the provided DOM element
export default function setupLeafletMap(mapElement) { export const setupLeafletMap = async (mapElement) => {
const Leaflet = (await import(/* webpackChunkName: "leaflet" */ "leaflet"))
.default;
Leaflet.Icon.Default.imagePath = "/static/images/leaflet";
const map = Leaflet.map(mapElement); const map = Leaflet.map(mapElement);
const style = document.createElement("link"); const style = document.createElement("link");
style.setAttribute("href", "/static/images/leaflet/leaflet.css"); style.setAttribute("href", "/static/images/leaflet/leaflet.css");
@ -21,5 +23,5 @@ export default function setupLeafletMap(mapElement) {
} }
).addTo(map); ).addTo(map);
return map; return [map, Leaflet];
} };

View File

@ -51,9 +51,13 @@ export type ActionConfig =
| MoreInfoActionConfig | MoreInfoActionConfig
| NoActionConfig; | NoActionConfig;
export const fetchConfig = (hass: HomeAssistant): Promise<LovelaceConfig> => export const fetchConfig = (
hass: HomeAssistant,
force: boolean
): Promise<LovelaceConfig> =>
hass.callWS({ hass.callWS({
type: "lovelace/config", type: "lovelace/config",
force,
}); });
export const migrateConfig = (hass: HomeAssistant): Promise<void> => export const migrateConfig = (hass: HomeAssistant): Promise<void> =>

View File

@ -8,18 +8,18 @@ import { TemplateResult } from "lit-html";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { styleMap } from "lit-html/directives/styleMap"; import { styleMap } from "lit-html/directives/styleMap";
import { jQuery } from "../../../resources/jquery";
import { roundSliderStyle } from "../../../resources/jquery.roundslider";
import { HomeAssistant, LightEntity } from "../../../types"; import { HomeAssistant, LightEntity } from "../../../types";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { LovelaceCard } from "../types"; import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace";
import { longPress } from "../common/directives/long-press-directive"; import { longPress } from "../common/directives/long-press-directive";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { loadRoundslider } from "../../../resources/jquery.roundslider.ondemand";
import { toggleEntity } from "../common/entity/toggle-entity";
import stateIcon from "../../../common/entity/state_icon"; import stateIcon from "../../../common/entity/state_icon";
import computeStateName from "../../../common/entity/compute_state_name"; import computeStateName from "../../../common/entity/compute_state_name";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
@ -49,11 +49,15 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
public hass?: HomeAssistant; public hass?: HomeAssistant;
private _config?: Config; private _config?: Config;
private _brightnessTimout?: number; private _brightnessTimout?: number;
private _roundSliderStyle?: TemplateResult;
private _jQuery?: any;
static get properties(): PropertyDeclarations { static get properties(): PropertyDeclarations {
return { return {
hass: {}, hass: {},
_config: {}, _config: {},
roundSliderStyle: {},
_jQuery: {},
}; };
} }
@ -99,14 +103,14 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
color: this._computeColor(stateObj), color: this._computeColor(stateObj),
}) })
}" }"
@ha-click="${() => this._handleClick(false)}" @ha-click="${this._handleTap}"
@ha-hold="${() => this._handleClick(true)}" @ha-hold="${this._handleHold}"
.longPress="${longPress()}" .longPress="${longPress()}"
></ha-icon> ></ha-icon>
<div <div
class="brightness" class="brightness"
@ha-click="${() => this._handleClick(false)}" @ha-click="${this._handleTap}"
@ha-hold="${() => this._handleClick(true)}" @ha-hold="${this._handleHold}"
.longPress="${longPress()}" .longPress="${longPress()}"
></div> ></div>
<div class="name"> <div class="name">
@ -124,10 +128,15 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
return hasConfigOrEntityChanged(this, changedProps); return hasConfigOrEntityChanged(this, changedProps);
} }
protected firstUpdated(): void { protected async firstUpdated(): Promise<void> {
const loaded = await loadRoundslider();
this._roundSliderStyle = loaded.roundSliderStyle;
this._jQuery = loaded.jQuery;
const brightness = this.hass!.states[this._config!.entity].attributes const brightness = this.hass!.states[this._config!.entity].attributes
.brightness; .brightness;
jQuery("#light", this.shadowRoot).roundSlider({ this._jQuery("#light", this.shadowRoot).roundSlider({
...lightConfig, ...lightConfig,
change: (value) => this._setBrightness(value), change: (value) => this._setBrightness(value),
drag: (value) => this._dragEvent(value), drag: (value) => this._dragEvent(value),
@ -139,13 +148,13 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
if (!this._config || !this.hass) { if (!this._config || !this.hass || !this._jQuery) {
return; return;
} }
const attrs = this.hass!.states[this._config!.entity].attributes; const attrs = this.hass!.states[this._config!.entity].attributes;
jQuery("#light", this.shadowRoot).roundSlider({ this._jQuery("#light", this.shadowRoot).roundSlider({
value: Math.round((attrs.brightness / 254) * 100) || 0, value: Math.round((attrs.brightness / 254) * 100) || 0,
}); });
@ -157,7 +166,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
private renderStyle(): TemplateResult { private renderStyle(): TemplateResult {
return html` return html`
${roundSliderStyle} ${this._roundSliderStyle}
<style> <style>
:host { :host {
display: block; display: block;
@ -311,18 +320,13 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
return `hsl(${hue}, 100%, ${100 - sat / 2}%)`; return `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
} }
private _handleClick(hold: boolean): void { private _handleTap() {
const entityId = this._config!.entity; toggleEntity(this.hass!, this._config!.entity!);
if (hold) {
fireEvent(this, "hass-more-info", {
entityId,
});
return;
} }
this.hass!.callService("light", "toggle", { private _handleHold() {
entity_id: entityId, fireEvent(this, "hass-more-info", {
entityId: this._config!.entity,
}); });
} }
} }

View File

@ -1,19 +1,16 @@
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import Leaflet from "leaflet";
import "../../map/ha-entity-marker"; import "../../map/ha-entity-marker";
import setupLeafletMap from "../../../common/dom/setup-leaflet-map"; import { setupLeafletMap } from "../../../common/dom/setup-leaflet-map";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
import computeStateDomain from "../../../common/entity/compute_state_domain"; import computeStateDomain from "../../../common/entity/compute_state_domain";
import computeStateName from "../../../common/entity/compute_state_name"; import computeStateName from "../../../common/entity/compute_state_name";
import debounce from "../../../common/util/debounce"; import debounce from "../../../common/util/debounce";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
Leaflet.Icon.Default.imagePath = "/static/images/leaflet";
class HuiMapCard extends PolymerElement { class HuiMapCard extends PolymerElement {
static get template() { static get template() {
return html` return html`
@ -143,13 +140,14 @@ class HuiMapCard extends PolymerElement {
window.addEventListener("resize", this._debouncedResizeListener); window.addEventListener("resize", this._debouncedResizeListener);
} }
this._map = setupLeafletMap(this.$.map); this.loadMap();
this._drawEntities(this.hass); }
setTimeout(() => { async loadMap() {
this._resetMap(); [this._map, this.Leaflet] = await setupLeafletMap(this.$.map);
this._drawEntities(this.hass);
this._map.invalidateSize();
this._fitMap(); this._fitMap();
}, 1);
} }
disconnectedCallback() { disconnectedCallback() {
@ -177,7 +175,7 @@ class HuiMapCard extends PolymerElement {
const zoom = this._config.default_zoom; const zoom = this._config.default_zoom;
if (this._mapItems.length === 0) { if (this._mapItems.length === 0) {
this._map.setView( this._map.setView(
new Leaflet.LatLng( new this.Leaflet.LatLng(
this.hass.config.latitude, this.hass.config.latitude,
this.hass.config.longitude this.hass.config.longitude
), ),
@ -186,7 +184,7 @@ class HuiMapCard extends PolymerElement {
return; return;
} }
const bounds = new Leaflet.latLngBounds( const bounds = new this.Leaflet.latLngBounds(
this._mapItems.map((item) => item.getLatLng()) this._mapItems.map((item) => item.getLatLng())
); );
this._map.fitBounds(bounds.pad(0.5)); this._map.fitBounds(bounds.pad(0.5));
@ -245,7 +243,7 @@ class HuiMapCard extends PolymerElement {
iconHTML = title; iconHTML = title;
} }
markerIcon = Leaflet.divIcon({ markerIcon = this.Leaflet.divIcon({
html: iconHTML, html: iconHTML,
iconSize: [24, 24], iconSize: [24, 24],
className: "", className: "",
@ -253,7 +251,7 @@ class HuiMapCard extends PolymerElement {
// create market with the icon // create market with the icon
mapItems.push( mapItems.push(
Leaflet.marker([latitude, longitude], { this.Leaflet.marker([latitude, longitude], {
icon: markerIcon, icon: markerIcon,
interactive: false, interactive: false,
title: title, title: title,
@ -262,7 +260,7 @@ class HuiMapCard extends PolymerElement {
// create circle around it // create circle around it
mapItems.push( mapItems.push(
Leaflet.circle([latitude, longitude], { this.Leaflet.circle([latitude, longitude], {
interactive: false, interactive: false,
color: "#FF9800", color: "#FF9800",
radius: radius, radius: radius,
@ -285,9 +283,9 @@ class HuiMapCard extends PolymerElement {
el.setAttribute("entity-name", entityName); el.setAttribute("entity-name", entityName);
el.setAttribute("entity-picture", entityPicture || ""); el.setAttribute("entity-picture", entityPicture || "");
/* Leaflet clones this element before adding it to the map. This messes up /* this.Leaflet clones this element before adding it to the map. This messes up
our Polymer object and we can't pass data through. Thus we hack like this. */ our Polymer object and we can't pass data through. Thus we hack like this. */
markerIcon = Leaflet.divIcon({ markerIcon = this.Leaflet.divIcon({
html: el.outerHTML, html: el.outerHTML,
iconSize: [48, 48], iconSize: [48, 48],
className: "", className: "",
@ -295,7 +293,7 @@ class HuiMapCard extends PolymerElement {
// create market with the icon // create market with the icon
mapItems.push( mapItems.push(
Leaflet.marker([latitude, longitude], { this.Leaflet.marker([latitude, longitude], {
icon: markerIcon, icon: markerIcon,
title: computeStateName(stateObj), title: computeStateName(stateObj),
}).addTo(map) }).addTo(map)
@ -304,7 +302,7 @@ class HuiMapCard extends PolymerElement {
// create circle around if entity has accuracy // create circle around if entity has accuracy
if (gpsAccuracy) { if (gpsAccuracy) {
mapItems.push( mapItems.push(
Leaflet.circle([latitude, longitude], { this.Leaflet.circle([latitude, longitude], {
interactive: false, interactive: false,
color: "#0288D1", color: "#0288D1",
radius: gpsAccuracy, radius: gpsAccuracy,

View File

@ -71,11 +71,13 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
.entity="${this._config.entity}" .entity="${this._config.entity}"
.aspectRatio="${this._config.aspect_ratio}" .aspectRatio="${this._config.aspect_ratio}"
></hui-image> ></hui-image>
<div id="root">
${ ${
this._config.elements.map((elementConfig: LovelaceElementConfig) => this._config.elements.map((elementConfig: LovelaceElementConfig) =>
this._createHuiElement(elementConfig) this._createHuiElement(elementConfig)
) )
} }
</div>
</ha-card> </ha-card>
`; `;
} }

View File

@ -6,13 +6,11 @@ import {
} from "@polymer/lit-element"; } from "@polymer/lit-element";
import { classMap } from "lit-html/directives/classMap"; import { classMap } from "lit-html/directives/classMap";
import { TemplateResult } from "lit-html"; import { TemplateResult } from "lit-html";
import { jQuery } from "../../../resources/jquery";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
import computeStateName from "../../../common/entity/compute_state_name"; import computeStateName from "../../../common/entity/compute_state_name";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import { roundSliderStyle } from "../../../resources/jquery.roundslider";
import { HomeAssistant, ClimateEntity } from "../../../types"; import { HomeAssistant, ClimateEntity } from "../../../types";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { LovelaceCard } from "../types"; import { LovelaceCard } from "../types";
@ -20,6 +18,7 @@ import { LovelaceCardConfig } from "../../../data/lovelace";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import { loadRoundslider } from "../../../resources/jquery.roundslider.ondemand";
const thermostatConfig = { const thermostatConfig = {
radius: 150, radius: 150,
@ -58,11 +57,15 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
implements LovelaceCard { implements LovelaceCard {
public hass?: HomeAssistant; public hass?: HomeAssistant;
private _config?: Config; private _config?: Config;
private _roundSliderStyle?: TemplateResult;
private _jQuery?: any;
static get properties(): PropertyDeclarations { static get properties(): PropertyDeclarations {
return { return {
hass: {}, hass: {},
_config: {}, _config: {},
roundSliderStyle: {},
_jQuery: {},
}; };
} }
@ -134,7 +137,12 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
return hasConfigOrEntityChanged(this, changedProps); return hasConfigOrEntityChanged(this, changedProps);
} }
protected firstUpdated(): void { protected async firstUpdated(): Promise<void> {
const loaded = await loadRoundslider();
this._roundSliderStyle = loaded.roundSliderStyle;
this._jQuery = loaded.jQuery;
const stateObj = this.hass!.states[this._config!.entity] as ClimateEntity; const stateObj = this.hass!.states[this._config!.entity] as ClimateEntity;
const _sliderType = const _sliderType =
@ -143,7 +151,7 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
? "range" ? "range"
: "min-range"; : "min-range";
jQuery("#thermostat", this.shadowRoot).roundSlider({ this._jQuery("#thermostat", this.shadowRoot).roundSlider({
...thermostatConfig, ...thermostatConfig,
radius: this.clientWidth / 3, radius: this.clientWidth / 3,
min: stateObj.attributes.min_temp, min: stateObj.attributes.min_temp,
@ -155,7 +163,7 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
if (!this._config || !this.hass) { if (!this._config || !this.hass || !this._jQuery) {
return; return;
} }
@ -179,7 +187,7 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
sliderValue = uiValue = stateObj.attributes.temperature; sliderValue = uiValue = stateObj.attributes.temperature;
} }
jQuery("#thermostat", this.shadowRoot).roundSlider({ this._jQuery("#thermostat", this.shadowRoot).roundSlider({
value: sliderValue, value: sliderValue,
}); });
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = uiValue; this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = uiValue;
@ -192,7 +200,7 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
private renderStyle(): TemplateResult { private renderStyle(): TemplateResult {
return html` return html`
${roundSliderStyle} ${this._roundSliderStyle}
<style> <style>
:host { :host {
display: block; display: block;

View File

@ -1,4 +1,4 @@
import "../../../cards/ha-camera-card"; import "../../../cards/ha-weather-card";
import LegacyWrapperCard from "./hui-legacy-wrapper-card"; import LegacyWrapperCard from "./hui-legacy-wrapper-card";

View File

@ -1,6 +1,7 @@
import computeStateName from "../../../common/entity/compute_state_name"; import computeStateName from "../../../common/entity/compute_state_name";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { LovelaceElementConfig } from "../elements/types"; import { LovelaceElementConfig } from "../elements/types";
import { ActionConfig } from "../../../data/lovelace";
export const computeTooltip = ( export const computeTooltip = (
hass: HomeAssistant, hass: HomeAssistant,
@ -11,7 +12,7 @@ export const computeTooltip = (
} }
let stateName = ""; let stateName = "";
let tooltip: string; let tooltip = "";
if (config.entity) { if (config.entity) {
stateName = stateName =
@ -20,19 +21,45 @@ export const computeTooltip = (
: config.entity; : config.entity;
} }
switch (config.tap_action && config.tap_action.action) { const tapTooltip = config.tap_action
case "navigate": ? computeActionTooltip(stateName, config.tap_action, false)
tooltip = `Navigate to ${config.navigation_path}`; : "";
break; const holdTooltip = config.hold_action
case "toggle": ? computeActionTooltip(stateName, config.hold_action, true)
tooltip = `Toggle ${stateName}`; : "";
break;
case "call-service": const newline = tapTooltip && holdTooltip ? "\n" : "";
tooltip = `Call service ${config.service}`;
break; tooltip = tapTooltip + newline + holdTooltip;
default:
tooltip = `Show more-info: ${stateName}`;
}
return tooltip; return tooltip;
}; };
function computeActionTooltip(
state: string,
config: ActionConfig,
isHold: boolean
) {
if (!config || !config.action || config.action === "none") {
return "";
}
let tooltip = isHold ? "Hold: " : "Tap: ";
switch (config.action) {
case "navigate":
tooltip += `Navigate to ${config.navigation_path}`;
break;
case "toggle":
tooltip += `Toggle ${state}`;
break;
case "call-service":
tooltip += `Call service ${config.service}`;
break;
case "more-info":
tooltip += `Show more-info: ${state}`;
break;
}
return tooltip;
}

View File

@ -1,7 +1,7 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { showEditCardDialog } from "../editor/hui-dialog-edit-card"; import { showEditCardDialog } from "../editor/show-edit-card-dialog";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { confDeleteCard } from "../editor/delete-card"; import { confDeleteCard } from "../editor/delete-card";
@ -50,14 +50,15 @@ export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) {
</style> </style>
<slot></slot> <slot></slot>
<div> <div>
<paper-button @click="${this._editCard}"
>${
this.localize("ui.panel.lovelace.editor.edit_card.edit")
}</paper-button
>
<paper-button class="warning" @click="${this._deleteCard}" <paper-button class="warning" @click="${this._deleteCard}"
>${ >${
this.localize("ui.panel.lovelace.editor.edit_card.delete") this.localize("ui.panel.lovelace.editor.edit_card.delete")
}</paper-button }</paper-button
><paper-button @click="${this._editCard}"
>${
this.localize("ui.panel.lovelace.editor.edit_card.edit")
}</paper-button
> >
</div> </div>
`; `;

View File

@ -1,5 +1,4 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import "@polymer/paper-button/paper-button";
import { TemplateResult } from "lit-html"; import { TemplateResult } from "lit-html";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
@ -84,9 +83,6 @@ export class HuiEntityEditor extends LitElement {
.entities { .entities {
padding-left: 20px; padding-left: 20px;
} }
paper-button {
margin: 8px 0;
}
</style> </style>
`; `;
} }

View File

@ -28,7 +28,8 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) {
if (!this._config) { if (!this._config) {
return ""; return "";
} }
return this._config.id || "";
return "id" in this._config ? this._config.id! : "";
} }
get _title(): string { get _title(): string {

View File

@ -2,7 +2,7 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html"; import { TemplateResult } from "lit-html";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
import { LovelaceCardConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace";
import "./hui-edit-card"; import "./hui-edit-card";
import "./hui-migrate-config"; import "./hui-migrate-config";
@ -11,7 +11,6 @@ declare global {
// for fire event // for fire event
interface HASSDomEvents { interface HASSDomEvents {
"reload-lovelace": undefined; "reload-lovelace": undefined;
"show-edit-card": EditCardDialogParams;
} }
// for add event listener // for add event listener
interface HTMLElementEventMap { interface HTMLElementEventMap {
@ -19,10 +18,6 @@ declare global {
} }
} }
let registeredDialog = false;
const dialogShowEvent = "show-edit-card";
const dialogTag = "hui-dialog-edit-card";
export interface EditCardDialogParams { export interface EditCardDialogParams {
cardConfig?: LovelaceCardConfig; cardConfig?: LovelaceCardConfig;
viewId?: string | number; viewId?: string | number;
@ -30,24 +25,6 @@ export interface EditCardDialogParams {
reloadLovelace: () => void; reloadLovelace: () => void;
} }
const registerEditCardDialog = (element: HTMLElement) =>
fireEvent(element, "register-dialog", {
dialogShowEvent,
dialogTag,
dialogImport: () => import("./hui-dialog-edit-card"),
});
export const showEditCardDialog = (
element: HTMLElement,
editCardDialogParams: EditCardDialogParams
) => {
if (!registeredDialog) {
registeredDialog = true;
registerEditCardDialog(element);
}
fireEvent(element, dialogShowEvent, editCardDialogParams);
};
export class HuiDialogEditCard extends LitElement { export class HuiDialogEditCard extends LitElement {
protected hass?: HomeAssistant; protected hass?: HomeAssistant;
private _params?: EditCardDialogParams; private _params?: EditCardDialogParams;
@ -110,4 +87,4 @@ declare global {
} }
} }
customElements.define(dialogTag, HuiDialogEditCard); customElements.define("hui-dialog-edit-card", HuiDialogEditCard);

View File

@ -2,16 +2,15 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html"; import { TemplateResult } from "lit-html";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
import { LovelaceViewConfig } from "../../../data/lovelace";
import "./hui-edit-view"; import "./hui-edit-view";
import "./hui-migrate-config"; import "./hui-migrate-config";
import { EditViewDialogParams } from "./show-edit-view-dialog";
declare global { declare global {
// for fire event // for fire event
interface HASSDomEvents { interface HASSDomEvents {
"reload-lovelace": undefined; "reload-lovelace": undefined;
"show-edit-view": EditViewDialogParams;
} }
// for add event listener // for add event listener
interface HTMLElementEventMap { interface HTMLElementEventMap {
@ -19,34 +18,6 @@ declare global {
} }
} }
let registeredDialog = false;
const dialogShowEvent = "show-edit-view";
const dialogTag = "hui-dialog-edit-view";
export interface EditViewDialogParams {
viewConfig?: LovelaceViewConfig;
add?: boolean;
reloadLovelace: () => void;
}
const registerEditViewDialog = (element: HTMLElement) =>
fireEvent(element, "register-dialog", {
dialogShowEvent,
dialogTag,
dialogImport: () => import("./hui-dialog-edit-view"),
});
export const showEditViewDialog = (
element: HTMLElement,
editViewDialogParams: EditViewDialogParams
) => {
if (!registeredDialog) {
registeredDialog = true;
registerEditViewDialog(element);
}
fireEvent(element, dialogShowEvent, editViewDialogParams);
};
export class HuiDialogEditView extends LitElement { export class HuiDialogEditView extends LitElement {
protected hass?: HomeAssistant; protected hass?: HomeAssistant;
private _params?: EditViewDialogParams; private _params?: EditViewDialogParams;
@ -71,7 +42,7 @@ export class HuiDialogEditView extends LitElement {
if ( if (
!this._params.add && !this._params.add &&
this._params.viewConfig && this._params.viewConfig &&
!this._params.viewConfig.id !("id" in this._params.viewConfig)
) { ) {
return html` return html`
<hui-migrate-config <hui-migrate-config
@ -98,4 +69,4 @@ declare global {
} }
} }
customElements.define(dialogTag, HuiDialogEditView); customElements.define("hui-dialog-edit-view", HuiDialogEditView);

View File

@ -10,6 +10,10 @@ import "@polymer/paper-spinner/paper-spinner";
import "@polymer/paper-tabs/paper-tab"; import "@polymer/paper-tabs/paper-tab";
import "@polymer/paper-tabs/paper-tabs"; import "@polymer/paper-tabs/paper-tabs";
import "@polymer/paper-dialog/paper-dialog"; import "@polymer/paper-dialog/paper-dialog";
import "@polymer/paper-icon-button/paper-icon-button.js";
import "@polymer/paper-item/paper-item.js";
import "@polymer/paper-listbox/paper-listbox.js";
import "@polymer/paper-menu-button/paper-menu-button.js";
// This is not a duplicate import, one is for types, one is for element. // This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line // tslint:disable-next-line
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog"; import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
@ -28,6 +32,8 @@ import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { EntitiesEditorEvent, ViewEditEvent } from "./types"; import { EntitiesEditorEvent, ViewEditEvent } from "./types";
import { processEditorEntities } from "./process-editor-entities"; import { processEditorEntities } from "./process-editor-entities";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig } from "../entity-rows/types";
import { confDeleteView } from "./delete-view";
import { navigate } from "../../../common/navigate";
export class HuiEditView extends hassLocalizeLitMixin(LitElement) { export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
static get properties(): PropertyDeclarations { static get properties(): PropertyDeclarations {
@ -79,7 +85,7 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
) { ) {
const { cards, badges, ...viewConfig } = this.viewConfig; const { cards, badges, ...viewConfig } = this.viewConfig;
this._config = viewConfig; this._config = viewConfig;
this._badges = processEditorEntities(badges); this._badges = badges ? processEditorEntities(badges) : [];
} else if (changedProperties.has("add")) { } else if (changedProperties.has("add")) {
this._config = {}; this._config = {};
this._badges = []; this._badges = [];
@ -146,6 +152,15 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
></paper-spinner> ></paper-spinner>
${this.localize("ui.common.save")}</paper-button ${this.localize("ui.common.save")}</paper-button
> >
<paper-menu-button no-animations>
<paper-icon-button
icon="hass:dots-vertical"
slot="dropdown-trigger"
></paper-icon-button>
<paper-listbox slot="dropdown-content">
<paper-item @click="${this._delete}">Delete</paper-item>
</paper-listbox>
</paper-menu-button>
</div> </div>
</paper-dialog> </paper-dialog>
`; `;
@ -189,6 +204,20 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
this._updateConfigInBackend(); this._updateConfigInBackend();
} }
private _delete() {
if (this._config!.cards && this._config!.cards!.length > 0) {
alert(
"You can't delete a view that has card in them. Remove the cards first."
);
return;
}
confDeleteView(this.hass!, this._config!.id!, () => {
this._closeDialog();
this.reloadLovelace!();
navigate(this, `/lovelace/0`);
});
}
private async _resizeDialog(): Promise<void> { private async _resizeDialog(): Promise<void> {
await this.updateComplete; await this.updateComplete;
fireEvent(this._dialog, "iron-resize"); fireEvent(this._dialog, "iron-resize");
@ -233,7 +262,7 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
} else { } else {
await updateViewConfig( await updateViewConfig(
this.hass!, this.hass!,
this.viewConfig!.id!, String(this.viewConfig!.id!),
this._config, this._config,
"json" "json"
); );

View File

@ -0,0 +1,38 @@
import { LovelaceCardConfig } from "../../../data/lovelace";
import { fireEvent } from "../../../common/dom/fire_event";
declare global {
// for fire event
interface HASSDomEvents {
"show-edit-card": EditCardDialogParams;
}
}
let registeredDialog = false;
const dialogShowEvent = "show-edit-card";
const dialogTag = "hui-dialog-edit-card";
export interface EditCardDialogParams {
cardConfig?: LovelaceCardConfig;
viewId?: string | number;
add: boolean;
reloadLovelace: () => void;
}
const registerEditCardDialog = (element: HTMLElement) =>
fireEvent(element, "register-dialog", {
dialogShowEvent,
dialogTag,
dialogImport: () => import("./hui-dialog-edit-card"),
});
export const showEditCardDialog = (
element: HTMLElement,
editCardDialogParams: EditCardDialogParams
) => {
if (!registeredDialog) {
registeredDialog = true;
registerEditCardDialog(element);
}
fireEvent(element, dialogShowEvent, editCardDialogParams);
};

View File

@ -0,0 +1,42 @@
import { HASSDomEvent, fireEvent } from "../../../common/dom/fire_event";
import { LovelaceViewConfig } from "../../../data/lovelace";
declare global {
// for fire event
interface HASSDomEvents {
"reload-lovelace": undefined;
"show-edit-view": EditViewDialogParams;
}
// for add event listener
interface HTMLElementEventMap {
"reload-lovelace": HASSDomEvent<undefined>;
}
}
let registeredDialog = false;
const dialogShowEvent = "show-edit-view";
const dialogTag = "hui-dialog-edit-view";
export interface EditViewDialogParams {
viewConfig?: LovelaceViewConfig;
add?: boolean;
reloadLovelace: () => void;
}
const registerEditViewDialog = (element: HTMLElement) =>
fireEvent(element, "register-dialog", {
dialogShowEvent,
dialogTag,
dialogImport: () => import("./hui-dialog-edit-view"),
});
export const showEditViewDialog = (
element: HTMLElement,
editViewDialogParams: EditViewDialogParams
) => {
if (!registeredDialog) {
registeredDialog = true;
registerEditViewDialog(element);
}
fireEvent(element, dialogShowEvent, editViewDialogParams);
};

View File

@ -8,7 +8,6 @@ export interface LovelaceElementConfig {
hold_action?: ActionConfig; hold_action?: ActionConfig;
service?: string; service?: string;
service_data?: object; service_data?: object;
navigation_path?: string;
tap_action?: ActionConfig; tap_action?: ActionConfig;
title?: string; title?: string;
} }

View File

@ -8,11 +8,15 @@ import "./hui-error-entity-row";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { EntityRow, EntityConfig } from "./types"; import { EntityRow, EntityConfig } from "./types";
import computeStateDisplay from "../../../common/entity/compute_state_display";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
interface SensorEntityConfig extends EntityConfig { interface SensorEntityConfig extends EntityConfig {
format?: "relative" | "date" | "time" | "datetime"; format?: "relative" | "date" | "time" | "datetime";
} }
class HuiSensorEntityRow extends LitElement implements EntityRow { class HuiSensorEntityRow extends hassLocalizeLitMixin(LitElement)
implements EntityRow {
public hass?: HomeAssistant; public hass?: HomeAssistant;
private _config?: SensorEntityConfig; private _config?: SensorEntityConfig;
@ -58,7 +62,7 @@ class HuiSensorEntityRow extends LitElement implements EntityRow {
.format="${this._config.format}" .format="${this._config.format}"
></hui-timestamp-display> ></hui-timestamp-display>
` `
: stateObj.state : computeStateDisplay(this.localize, stateObj, this.hass.language)
} }
</div> </div>
</hui-generic-entity-row> </hui-generic-entity-row>

View File

@ -28,7 +28,7 @@ class Lovelace extends localizeMixin(PolymerElement) {
route="[[route]]" route="[[route]]"
config="[[_config]]" config="[[_config]]"
columns="[[_columns]]" columns="[[_columns]]"
on-config-refresh="_fetchConfig" on-config-refresh="_forceFetchConfig"
></hui-root> ></hui-root>
</template> </template>
<template <template
@ -48,7 +48,7 @@ class Lovelace extends localizeMixin(PolymerElement) {
narrow="[[narrow]]" narrow="[[narrow]]"
show-menu="[[showMenu]]" show-menu="[[showMenu]]"
> >
<paper-button on-click="_fetchConfig" <paper-button on-click="_forceFetchConfig"
>Reload ui-lovelace.yaml</paper-button >Reload ui-lovelace.yaml</paper-button
> >
</hass-error-screen> </hass-error-screen>
@ -96,7 +96,7 @@ class Lovelace extends localizeMixin(PolymerElement) {
} }
ready() { ready() {
this._fetchConfig(); this._fetchConfig(false);
this._updateColumns = this._updateColumns.bind(this); this._updateColumns = this._updateColumns.bind(this);
this.mqls = [300, 600, 900, 1200].map((width) => { this.mqls = [300, 600, 900, 1200].map((width) => {
const mql = matchMedia(`(min-width: ${width}px)`); const mql = matchMedia(`(min-width: ${width}px)`);
@ -113,9 +113,13 @@ class Lovelace extends localizeMixin(PolymerElement) {
this._columns = Math.max(1, matchColumns - (!this.narrow && this.showMenu)); this._columns = Math.max(1, matchColumns - (!this.narrow && this.showMenu));
} }
async _fetchConfig() { _forceFetchConfig() {
this._fetchConfig(true);
}
async _fetchConfig(force) {
try { try {
const conf = await fetchConfig(this.hass); const conf = await fetchConfig(this.hass, force);
this.setProperties({ this.setProperties({
_config: conf, _config: conf,
_state: "loaded", _state: "loaded",

View File

@ -33,8 +33,7 @@ import "./hui-view";
import debounce from "../../common/util/debounce"; import debounce from "../../common/util/debounce";
import createCardElement from "./common/create-card-element"; import createCardElement from "./common/create-card-element";
import { showSaveDialog } from "./editor/hui-dialog-save-config"; import { showSaveDialog } from "./editor/hui-dialog-save-config";
import { showEditViewDialog } from "./editor/hui-dialog-edit-view"; import { showEditViewDialog } from "./editor/show-edit-view-dialog";
import { confDeleteView } from "./editor/delete-view";
// CSS and JS should only be imported once. Modules and HTML are safe. // CSS and JS should only be imported once. Modules and HTML are safe.
const CSS_CACHE = {}; const CSS_CACHE = {};
@ -61,7 +60,6 @@ class HUIRoot extends NavigateMixin(
text-transform: uppercase; text-transform: uppercase;
} }
#add-view { #add-view {
background: var(--paper-fab-background, var(--accent-color));
position: absolute; position: absolute;
height: 44px; height: 44px;
} }
@ -71,12 +69,9 @@ class HUIRoot extends NavigateMixin(
paper-button.warning:not([disabled]) { paper-button.warning:not([disabled]) {
color: var(--google-red-500); color: var(--google-red-500);
} }
app-toolbar.secondary { #add-view ha-icon {
background-color: var(--light-primary-color); background-color: var(--accent-color);
color: var(--primary-text-color, #333); border-radius: 5px;
font-size: 14px;
font-weight: 500;
height: auto;
} }
#view { #view {
min-height: calc(100vh - 112px); min-height: calc(100vh - 112px);
@ -95,6 +90,9 @@ class HUIRoot extends NavigateMixin(
paper-item { paper-item {
cursor: pointer; cursor: pointer;
} }
.edit-view-icon {
padding-left: 8px;
}
</style> </style>
<app-route route="[[route]]" pattern="/:view" data="{{routeData}}"></app-route> <app-route route="[[route]]" pattern="/:view" data="{{routeData}}"></app-route>
<hui-notification-drawer <hui-notification-drawer
@ -140,7 +138,7 @@ class HUIRoot extends NavigateMixin(
</app-toolbar> </app-toolbar>
</template> </template>
<div sticky hidden$="[[_computeTabsHidden(config.views)]]"> <div sticky hidden$="[[_computeTabsHidden(config.views, _editMode)]]">
<paper-tabs scrollable selected="[[_curView]]" on-iron-activate="_handleViewSelected"> <paper-tabs scrollable selected="[[_curView]]" on-iron-activate="_handleViewSelected">
<template is="dom-repeat" items="[[config.views]]"> <template is="dom-repeat" items="[[config.views]]">
<paper-tab> <paper-tab>
@ -150,6 +148,9 @@ class HUIRoot extends NavigateMixin(
<template is="dom-if" if="[[!item.icon]]"> <template is="dom-if" if="[[!item.icon]]">
[[_computeTabTitle(item.title)]] [[_computeTabTitle(item.title)]]
</template> </template>
<template is='dom-if' if="[[_computeEditVisible(_editMode, config.views)]]">
<ha-icon class="edit-view-icon" on-click="_editView" icon="hass:pencil"></ha-icon>
</template>
</paper-tab> </paper-tab>
</template> </template>
<template is='dom-if' if="[[_editMode]]"> <template is='dom-if' if="[[_editMode]]">
@ -160,12 +161,6 @@ class HUIRoot extends NavigateMixin(
</paper-tabs> </paper-tabs>
</div> </div>
</app-header> </app-header>
<template is='dom-if' if="[[_editMode]]">
<app-toolbar class="secondary">
<paper-button on-click="_editView">[[localize("ui.panel.lovelace.editor.edit_view.edit")]]</paper-button>
<paper-button class="warning" on-click="_deleteView">[[localize("ui.panel.lovelace.editor.edit_view.delete")]]</paper-button>
</app-toolbar>
</template>
<div id='view' on-rebuild-view='_debouncedConfigChanged'></div> <div id='view' on-rebuild-view='_debouncedConfigChanged'></div>
</app-header-layout> </app-header-layout>
`; `;
@ -280,8 +275,8 @@ class HUIRoot extends NavigateMixin(
return config.title || "Home Assistant"; return config.title || "Home Assistant";
} }
_computeTabsHidden(views) { _computeTabsHidden(views, editMode) {
return views.length < 2; return views.length < 2 && !editMode;
} }
_computeTabTitle(title) { _computeTabTitle(title) {
@ -304,6 +299,10 @@ class HUIRoot extends NavigateMixin(
window.open("https://www.home-assistant.io/lovelace/", "_blank"); window.open("https://www.home-assistant.io/lovelace/", "_blank");
} }
_computeEditVisible(editMode, views) {
return editMode && views[this._curView];
}
_editModeEnable() { _editModeEnable() {
if (this.config._frontendAuto) { if (this.config._frontendAuto) {
showSaveDialog(this, { showSaveDialog(this, {
@ -316,10 +315,18 @@ class HUIRoot extends NavigateMixin(
return; return;
} }
this._editMode = true; this._editMode = true;
if (this.config.views.length < 2) {
this.$.view.classList.remove("tabs-hidden");
this.fire("iron-resize");
}
} }
_editModeDisable() { _editModeDisable() {
this._editMode = false; this._editMode = false;
if (this.config.views.length < 2) {
this.$.view.classList.add("tabs-hidden");
this.fire("iron-resize");
}
} }
_editModeChanged() { _editModeChanged() {
@ -345,24 +352,6 @@ class HUIRoot extends NavigateMixin(
}); });
} }
_deleteView() {
const viewConfig = this.config.views[this._curView];
if (viewConfig.cards && viewConfig.cards.length > 0) {
alert(
"You can't delete a view that has card in them. Remove the cards first."
);
return;
}
if (!viewConfig.id) {
this._editView();
return;
}
confDeleteView(this.hass, viewConfig.id, () => {
this.fire("config-refresh");
this._navigateView(0);
});
}
_handleViewSelected(ev) { _handleViewSelected(ev) {
const index = ev.detail.selected; const index = ev.detail.selected;
this._navigateView(index); this._navigateView(index);
@ -393,6 +382,10 @@ class HUIRoot extends NavigateMixin(
view.setConfig(this.config); view.setConfig(this.config);
} else { } else {
const viewConfig = this.config.views[this._curView]; const viewConfig = this.config.views[this._curView];
if (!viewConfig) {
this._editModeEnable();
return;
}
if (viewConfig.panel) { if (viewConfig.panel) {
view = createCardElement(viewConfig.cards[0]); view = createCardElement(viewConfig.cards[0]);
view.isPanel = true; view.isPanel = true;

View File

@ -11,7 +11,7 @@ import EventsMixin from "../../mixins/events-mixin";
import localizeMixin from "../../mixins/localize-mixin"; import localizeMixin from "../../mixins/localize-mixin";
import createCardElement from "./common/create-card-element"; import createCardElement from "./common/create-card-element";
import { computeCardSize } from "./common/compute-card-size"; import { computeCardSize } from "./common/compute-card-size";
import { showEditCardDialog } from "./editor/hui-dialog-edit-card"; import { showEditCardDialog } from "./editor/show-edit-card-dialog";
class HUIView extends localizeMixin(EventsMixin(PolymerElement)) { class HUIView extends localizeMixin(EventsMixin(PolymerElement)) {
static get template() { static get template() {
@ -119,7 +119,7 @@ class HUIView extends localizeMixin(EventsMixin(PolymerElement)) {
_addCard() { _addCard() {
showEditCardDialog(this, { showEditCardDialog(this, {
viewId: this.config.id, viewId: "id" in this.config ? String(this.config.id) : undefined,
add: true, add: true,
reloadLovelace: () => { reloadLovelace: () => {
this.fire("config-refresh"); this.fire("config-refresh");

View File

@ -1,7 +1,6 @@
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import Leaflet from "leaflet";
import "../../components/ha-menu-button"; import "../../components/ha-menu-button";
import "../../components/ha-icon"; import "../../components/ha-icon";
@ -11,9 +10,7 @@ import "./ha-entity-marker";
import computeStateDomain from "../../common/entity/compute_state_domain"; import computeStateDomain from "../../common/entity/compute_state_domain";
import computeStateName from "../../common/entity/compute_state_name"; import computeStateName from "../../common/entity/compute_state_name";
import LocalizeMixin from "../../mixins/localize-mixin"; import LocalizeMixin from "../../mixins/localize-mixin";
import setupLeafletMap from "../../common/dom/setup-leaflet-map"; import { setupLeafletMap } from "../../common/dom/setup-leaflet-map";
Leaflet.Icon.Default.imagePath = "/static/images/leaflet";
/* /*
* @appliesMixin LocalizeMixin * @appliesMixin LocalizeMixin
@ -61,14 +58,14 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
var map = (this._map = setupLeafletMap(this.$.map)); this.loadMap();
}
async loadMap() {
[this._map, this.Leaflet] = await setupLeafletMap(this.$.map);
this.drawEntities(this.hass); this.drawEntities(this.hass);
this._map.invalidateSize();
setTimeout(() => {
map.invalidateSize();
this.fitMap(); this.fitMap();
}, 1);
} }
disconnectedCallback() { disconnectedCallback() {
@ -82,14 +79,14 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
if (this._mapItems.length === 0) { if (this._mapItems.length === 0) {
this._map.setView( this._map.setView(
new Leaflet.LatLng( new this.Leaflet.LatLng(
this.hass.config.latitude, this.hass.config.latitude,
this.hass.config.longitude this.hass.config.longitude
), ),
14 14
); );
} else { } else {
bounds = new Leaflet.latLngBounds( bounds = new this.Leaflet.latLngBounds(
this._mapItems.map((item) => item.getLatLng()) this._mapItems.map((item) => item.getLatLng())
); );
this._map.fitBounds(bounds.pad(0.5)); this._map.fitBounds(bounds.pad(0.5));
@ -108,7 +105,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
} }
var mapItems = (this._mapItems = []); var mapItems = (this._mapItems = []);
Object.keys(hass.states).forEach(function(entityId) { Object.keys(hass.states).forEach((entityId) => {
var entity = hass.states[entityId]; var entity = hass.states[entityId];
var title = computeStateName(entity); var title = computeStateName(entity);
@ -137,7 +134,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
iconHTML = title; iconHTML = title;
} }
icon = Leaflet.divIcon({ icon = this.Leaflet.divIcon({
html: iconHTML, html: iconHTML,
iconSize: [24, 24], iconSize: [24, 24],
className: "", className: "",
@ -145,7 +142,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
// create market with the icon // create market with the icon
mapItems.push( mapItems.push(
Leaflet.marker( this.Leaflet.marker(
[entity.attributes.latitude, entity.attributes.longitude], [entity.attributes.latitude, entity.attributes.longitude],
{ {
icon: icon, icon: icon,
@ -157,7 +154,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
// create circle around it // create circle around it
mapItems.push( mapItems.push(
Leaflet.circle( this.Leaflet.circle(
[entity.attributes.latitude, entity.attributes.longitude], [entity.attributes.latitude, entity.attributes.longitude],
{ {
interactive: false, interactive: false,
@ -181,7 +178,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
.join(""); .join("");
/* Leaflet clones this element before adding it to the map. This messes up /* Leaflet clones this element before adding it to the map. This messes up
our Polymer object and we can't pass data through. Thus we hack like this. */ our Polymer object and we can't pass data through. Thus we hack like this. */
icon = Leaflet.divIcon({ icon = this.Leaflet.divIcon({
html: html:
"<ha-entity-marker entity-id='" + "<ha-entity-marker entity-id='" +
entity.entity_id + entity.entity_id +
@ -196,7 +193,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
// create market with the icon // create market with the icon
mapItems.push( mapItems.push(
Leaflet.marker( this.Leaflet.marker(
[entity.attributes.latitude, entity.attributes.longitude], [entity.attributes.latitude, entity.attributes.longitude],
{ {
icon: icon, icon: icon,
@ -208,7 +205,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
// create circle around if entity has accuracy // create circle around if entity has accuracy
if (entity.attributes.gps_accuracy) { if (entity.attributes.gps_accuracy) {
mapItems.push( mapItems.push(
Leaflet.circle( this.Leaflet.circle(
[entity.attributes.latitude, entity.attributes.longitude], [entity.attributes.latitude, entity.attributes.longitude],
{ {
interactive: false, interactive: false,

View File

@ -1,8 +1,12 @@
import { html } from "@polymer/lit-element"; import { html } from "@polymer/lit-element";
import "./jquery"; // jQuery import should come before plugin import
import { jQuery as jQuery_ } from "./jquery";
import "round-slider"; import "round-slider";
// eslint-disable-next-line
import roundSliderCSS from "round-slider/dist/roundslider.min.css"; import roundSliderCSS from "round-slider/dist/roundslider.min.css";
export const jQuery = jQuery_;
export const roundSliderStyle = html` export const roundSliderStyle = html`
<style> <style>
${roundSliderCSS} ${roundSliderCSS}

View File

@ -0,0 +1,15 @@
import { TemplateResult } from "lit-html";
type LoadedRoundSlider = Promise<{
roundSliderStyle: TemplateResult;
jQuery: any;
}>;
let loaded: LoadedRoundSlider;
export const loadRoundslider = async (): LoadedRoundSlider => {
if (!loaded) {
loaded = import(/* webpackChunkName: "jquery-roundslider" */ "./jquery.roundslider");
}
return loaded;
};

View File

@ -16,6 +16,7 @@ if (!version) {
} }
const VERSION = version[0]; const VERSION = version[0];
const isCI = process.env.CI === "true"; const isCI = process.env.CI === "true";
const isStatsBuild = process.env.STATS === "1";
const generateJSPage = (entrypoint, latestBuild) => { const generateJSPage = (entrypoint, latestBuild) => {
return new HtmlWebpackPlugin({ return new HtmlWebpackPlugin({
@ -51,10 +52,6 @@ function createConfig(isProdBuild, latestBuild) {
entry["service-worker-hass"] = "./src/entrypoints/service-worker-hass.js"; entry["service-worker-hass"] = "./src/entrypoints/service-worker-hass.js";
} }
const chunkFilename = isProdBuild
? "[chunkhash].chunk.js"
: "[name].chunk.js";
return { return {
mode: isProdBuild ? "production" : "development", mode: isProdBuild ? "production" : "development",
devtool: isProdBuild devtool: isProdBuild
@ -161,6 +158,7 @@ function createConfig(isProdBuild, latestBuild) {
), ),
isProdBuild && isProdBuild &&
!isCI && !isCI &&
!isStatsBuild &&
new CompressionPlugin({ new CompressionPlugin({
cache: true, cache: true,
exclude: [/\.js\.map$/, /\.LICENSE$/, /\.py$/, /\.txt$/], exclude: [/\.js\.map$/, /\.LICENSE$/, /\.py$/, /\.txt$/],
@ -223,7 +221,10 @@ function createConfig(isProdBuild, latestBuild) {
if (!isProdBuild || dontHash.has(chunk.name)) return `${chunk.name}.js`; if (!isProdBuild || dontHash.has(chunk.name)) return `${chunk.name}.js`;
return `${chunk.name}-${chunk.hash.substr(0, 8)}.js`; return `${chunk.name}-${chunk.hash.substr(0, 8)}.js`;
}, },
chunkFilename: chunkFilename, chunkFilename:
isProdBuild && !isStatsBuild
? "[chunkhash].chunk.js"
: "[name].chunk.js",
path: path.resolve(__dirname, buildPath), path: path.resolve(__dirname, buildPath),
publicPath, publicPath,
}, },
@ -243,7 +244,7 @@ function createConfig(isProdBuild, latestBuild) {
const isProdBuild = process.env.NODE_ENV === "production"; const isProdBuild = process.env.NODE_ENV === "production";
const configs = [createConfig(isProdBuild, /* latestBuild */ true)]; const configs = [createConfig(isProdBuild, /* latestBuild */ true)];
if (isProdBuild) { if (isProdBuild && !isStatsBuild) {
configs.push(createConfig(isProdBuild, /* latestBuild */ false)); configs.push(createConfig(isProdBuild, /* latestBuild */ false));
} }
module.exports = configs; module.exports = configs;