From 2fe1d04eb0236709c32accb7dbdc3bd3b8758324 Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Thu, 6 Dec 2018 04:49:19 -0600 Subject: [PATCH 01/16] Action tooltips (#2193) * Fix tooltips for tap actions and add hold actions * Cleanup logic --- src/panels/lovelace/common/compute-tooltip.ts | 55 ++++++++++++++----- src/panels/lovelace/elements/types.ts | 1 - 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/panels/lovelace/common/compute-tooltip.ts b/src/panels/lovelace/common/compute-tooltip.ts index 7ab9830223..2a7b7129c3 100644 --- a/src/panels/lovelace/common/compute-tooltip.ts +++ b/src/panels/lovelace/common/compute-tooltip.ts @@ -1,6 +1,7 @@ import computeStateName from "../../../common/entity/compute_state_name"; import { HomeAssistant } from "../../../types"; import { LovelaceElementConfig } from "../elements/types"; +import { ActionConfig } from "../../../data/lovelace"; export const computeTooltip = ( hass: HomeAssistant, @@ -11,7 +12,7 @@ export const computeTooltip = ( } let stateName = ""; - let tooltip: string; + let tooltip = ""; if (config.entity) { stateName = @@ -20,19 +21,45 @@ export const computeTooltip = ( : config.entity; } - switch (config.tap_action && config.tap_action.action) { - case "navigate": - tooltip = `Navigate to ${config.navigation_path}`; - break; - case "toggle": - tooltip = `Toggle ${stateName}`; - break; - case "call-service": - tooltip = `Call service ${config.service}`; - break; - default: - tooltip = `Show more-info: ${stateName}`; - } + const tapTooltip = config.tap_action + ? computeActionTooltip(stateName, config.tap_action, false) + : ""; + const holdTooltip = config.hold_action + ? computeActionTooltip(stateName, config.hold_action, true) + : ""; + + const newline = tapTooltip && holdTooltip ? "\n" : ""; + + tooltip = tapTooltip + newline + holdTooltip; 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; +} diff --git a/src/panels/lovelace/elements/types.ts b/src/panels/lovelace/elements/types.ts index 0eb49c5308..ebc66605d9 100644 --- a/src/panels/lovelace/elements/types.ts +++ b/src/panels/lovelace/elements/types.ts @@ -8,7 +8,6 @@ export interface LovelaceElementConfig { hold_action?: ActionConfig; service?: string; service_data?: object; - navigation_path?: string; tap_action?: ActionConfig; title?: string; } From bbe90c168392cfac0bec1959143d1a9090f65dd6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 6 Dec 2018 11:49:33 +0100 Subject: [PATCH 02/16] Fix view without badges (#2192) --- src/panels/lovelace/editor/hui-edit-view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/editor/hui-edit-view.ts b/src/panels/lovelace/editor/hui-edit-view.ts index a6ed38966d..b73f3aca6a 100644 --- a/src/panels/lovelace/editor/hui-edit-view.ts +++ b/src/panels/lovelace/editor/hui-edit-view.ts @@ -79,7 +79,7 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) { ) { const { cards, badges, ...viewConfig } = this.viewConfig; this._config = viewConfig; - this._badges = processEditorEntities(badges); + this._badges = badges ? processEditorEntities(badges) : []; } else if (changedProperties.has("add")) { this._config = {}; this._badges = []; From be3bfc7aa492f2c7273c16b4df0a4ad89ed20655 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 6 Dec 2018 12:43:43 +0100 Subject: [PATCH 03/16] Add script to show stats (#2195) --- script/develop | 2 ++ script/size_stats | 11 +++++++++++ webpack.config.js | 13 +++++++------ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100755 script/size_stats diff --git a/script/develop b/script/develop index 59d778a2f2..24c1253dc0 100755 --- a/script/develop +++ b/script/develop @@ -4,6 +4,8 @@ # Stop on errors set -e +cd "$(dirname "$0")/.." + BUILD_DIR=build OUTPUT_DIR=hass_frontend OUTPUT_DIR_ES5=hass_frontend_es5 diff --git a/script/size_stats b/script/size_stats new file mode 100755 index 0000000000..4b6601a480 --- /dev/null +++ b/script/size_stats @@ -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 diff --git a/webpack.config.js b/webpack.config.js index 4bc3fb1cc4..261e72768a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,6 +16,7 @@ if (!version) { } const VERSION = version[0]; const isCI = process.env.CI === "true"; +const isStatsBuild = process.env.STATS === "1"; const generateJSPage = (entrypoint, latestBuild) => { return new HtmlWebpackPlugin({ @@ -51,10 +52,6 @@ function createConfig(isProdBuild, latestBuild) { entry["service-worker-hass"] = "./src/entrypoints/service-worker-hass.js"; } - const chunkFilename = isProdBuild - ? "[chunkhash].chunk.js" - : "[name].chunk.js"; - return { mode: isProdBuild ? "production" : "development", devtool: isProdBuild @@ -161,6 +158,7 @@ function createConfig(isProdBuild, latestBuild) { ), isProdBuild && !isCI && + !isStatsBuild && new CompressionPlugin({ cache: true, exclude: [/\.js\.map$/, /\.LICENSE$/, /\.py$/, /\.txt$/], @@ -223,7 +221,10 @@ function createConfig(isProdBuild, latestBuild) { if (!isProdBuild || dontHash.has(chunk.name)) return `${chunk.name}.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), publicPath, }, @@ -243,7 +244,7 @@ function createConfig(isProdBuild, latestBuild) { const isProdBuild = process.env.NODE_ENV === "production"; const configs = [createConfig(isProdBuild, /* latestBuild */ true)]; -if (isProdBuild) { +if (isProdBuild && !isStatsBuild) { configs.push(createConfig(isProdBuild, /* latestBuild */ false)); } module.exports = configs; From 6e3c2bfd6aef43cb35b8553c28f9d8c8933ce8ce Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 6 Dec 2018 13:30:05 +0100 Subject: [PATCH 04/16] Code split Leaflet (#2196) --- src/common/dom/setup-leaflet-map.js | 12 ++++---- src/panels/lovelace/cards/hui-map-card.js | 36 +++++++++++------------ src/panels/map/ha-panel-map.js | 35 ++++++++++------------ 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/common/dom/setup-leaflet-map.js b/src/common/dom/setup-leaflet-map.js index de1a0109c0..7649161de0 100644 --- a/src/common/dom/setup-leaflet-map.js +++ b/src/common/dom/setup-leaflet-map.js @@ -1,7 +1,9 @@ -import Leaflet from "leaflet"; - // 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 style = document.createElement("link"); style.setAttribute("href", "/static/images/leaflet/leaflet.css"); @@ -21,5 +23,5 @@ export default function setupLeafletMap(mapElement) { } ).addTo(map); - return map; -} + return [map, Leaflet]; +}; diff --git a/src/panels/lovelace/cards/hui-map-card.js b/src/panels/lovelace/cards/hui-map-card.js index cb52efe46d..32a7631c6d 100644 --- a/src/panels/lovelace/cards/hui-map-card.js +++ b/src/panels/lovelace/cards/hui-map-card.js @@ -1,19 +1,16 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import "@polymer/paper-icon-button/paper-icon-button"; -import Leaflet from "leaflet"; 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 computeStateDomain from "../../../common/entity/compute_state_domain"; import computeStateName from "../../../common/entity/compute_state_name"; import debounce from "../../../common/util/debounce"; import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; -Leaflet.Icon.Default.imagePath = "/static/images/leaflet"; - class HuiMapCard extends PolymerElement { static get template() { return html` @@ -143,13 +140,14 @@ class HuiMapCard extends PolymerElement { window.addEventListener("resize", this._debouncedResizeListener); } - this._map = setupLeafletMap(this.$.map); - this._drawEntities(this.hass); + this.loadMap(); + } - setTimeout(() => { - this._resetMap(); - this._fitMap(); - }, 1); + async loadMap() { + [this._map, this.Leaflet] = await setupLeafletMap(this.$.map); + this._drawEntities(this.hass); + this._map.invalidateSize(); + this._fitMap(); } disconnectedCallback() { @@ -177,7 +175,7 @@ class HuiMapCard extends PolymerElement { const zoom = this._config.default_zoom; if (this._mapItems.length === 0) { this._map.setView( - new Leaflet.LatLng( + new this.Leaflet.LatLng( this.hass.config.latitude, this.hass.config.longitude ), @@ -186,7 +184,7 @@ class HuiMapCard extends PolymerElement { return; } - const bounds = new Leaflet.latLngBounds( + const bounds = new this.Leaflet.latLngBounds( this._mapItems.map((item) => item.getLatLng()) ); this._map.fitBounds(bounds.pad(0.5)); @@ -245,7 +243,7 @@ class HuiMapCard extends PolymerElement { iconHTML = title; } - markerIcon = Leaflet.divIcon({ + markerIcon = this.Leaflet.divIcon({ html: iconHTML, iconSize: [24, 24], className: "", @@ -253,7 +251,7 @@ class HuiMapCard extends PolymerElement { // create market with the icon mapItems.push( - Leaflet.marker([latitude, longitude], { + this.Leaflet.marker([latitude, longitude], { icon: markerIcon, interactive: false, title: title, @@ -262,7 +260,7 @@ class HuiMapCard extends PolymerElement { // create circle around it mapItems.push( - Leaflet.circle([latitude, longitude], { + this.Leaflet.circle([latitude, longitude], { interactive: false, color: "#FF9800", radius: radius, @@ -285,9 +283,9 @@ class HuiMapCard extends PolymerElement { el.setAttribute("entity-name", entityName); 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. */ - markerIcon = Leaflet.divIcon({ + markerIcon = this.Leaflet.divIcon({ html: el.outerHTML, iconSize: [48, 48], className: "", @@ -295,7 +293,7 @@ class HuiMapCard extends PolymerElement { // create market with the icon mapItems.push( - Leaflet.marker([latitude, longitude], { + this.Leaflet.marker([latitude, longitude], { icon: markerIcon, title: computeStateName(stateObj), }).addTo(map) @@ -304,7 +302,7 @@ class HuiMapCard extends PolymerElement { // create circle around if entity has accuracy if (gpsAccuracy) { mapItems.push( - Leaflet.circle([latitude, longitude], { + this.Leaflet.circle([latitude, longitude], { interactive: false, color: "#0288D1", radius: gpsAccuracy, diff --git a/src/panels/map/ha-panel-map.js b/src/panels/map/ha-panel-map.js index 0a13829de7..6110c89064 100644 --- a/src/panels/map/ha-panel-map.js +++ b/src/panels/map/ha-panel-map.js @@ -1,7 +1,6 @@ import "@polymer/app-layout/app-toolbar/app-toolbar"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; -import Leaflet from "leaflet"; import "../../components/ha-menu-button"; import "../../components/ha-icon"; @@ -11,9 +10,7 @@ import "./ha-entity-marker"; import computeStateDomain from "../../common/entity/compute_state_domain"; import computeStateName from "../../common/entity/compute_state_name"; import LocalizeMixin from "../../mixins/localize-mixin"; -import setupLeafletMap from "../../common/dom/setup-leaflet-map"; - -Leaflet.Icon.Default.imagePath = "/static/images/leaflet"; +import { setupLeafletMap } from "../../common/dom/setup-leaflet-map"; /* * @appliesMixin LocalizeMixin @@ -61,14 +58,14 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { 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); - - setTimeout(() => { - map.invalidateSize(); - this.fitMap(); - }, 1); + this._map.invalidateSize(); + this.fitMap(); } disconnectedCallback() { @@ -82,14 +79,14 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { if (this._mapItems.length === 0) { this._map.setView( - new Leaflet.LatLng( + new this.Leaflet.LatLng( this.hass.config.latitude, this.hass.config.longitude ), 14 ); } else { - bounds = new Leaflet.latLngBounds( + bounds = new this.Leaflet.latLngBounds( this._mapItems.map((item) => item.getLatLng()) ); this._map.fitBounds(bounds.pad(0.5)); @@ -108,7 +105,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { } var mapItems = (this._mapItems = []); - Object.keys(hass.states).forEach(function(entityId) { + Object.keys(hass.states).forEach((entityId) => { var entity = hass.states[entityId]; var title = computeStateName(entity); @@ -137,7 +134,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { iconHTML = title; } - icon = Leaflet.divIcon({ + icon = this.Leaflet.divIcon({ html: iconHTML, iconSize: [24, 24], className: "", @@ -145,7 +142,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { // create market with the icon mapItems.push( - Leaflet.marker( + this.Leaflet.marker( [entity.attributes.latitude, entity.attributes.longitude], { icon: icon, @@ -157,7 +154,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { // create circle around it mapItems.push( - Leaflet.circle( + this.Leaflet.circle( [entity.attributes.latitude, entity.attributes.longitude], { interactive: false, @@ -181,7 +178,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { .join(""); /* 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. */ - icon = Leaflet.divIcon({ + icon = this.Leaflet.divIcon({ html: " + +