diff --git a/gallery/src/demos/demo-hui-alarm-panel-card.js b/gallery/src/demos/demo-hui-alarm-panel-card.ts similarity index 98% rename from gallery/src/demos/demo-hui-alarm-panel-card.js rename to gallery/src/demos/demo-hui-alarm-panel-card.ts index fb943186d6..11feab3b9a 100644 --- a/gallery/src/demos/demo-hui-alarm-panel-card.js +++ b/gallery/src/demos/demo-hui-alarm-panel-card.ts @@ -69,7 +69,7 @@ class DemoAlarmPanelEntity extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-conditional-card.js b/gallery/src/demos/demo-hui-conditional-card.ts similarity index 98% rename from gallery/src/demos/demo-hui-conditional-card.js rename to gallery/src/demos/demo-hui-conditional-card.ts index c3dc1193eb..6a774061ac 100644 --- a/gallery/src/demos/demo-hui-conditional-card.js +++ b/gallery/src/demos/demo-hui-conditional-card.ts @@ -74,7 +74,7 @@ class DemoConditional extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-entities-card.js b/gallery/src/demos/demo-hui-entities-card.ts similarity index 99% rename from gallery/src/demos/demo-hui-entities-card.js rename to gallery/src/demos/demo-hui-entities-card.ts index 006df1653f..32f137d6e1 100644 --- a/gallery/src/demos/demo-hui-entities-card.js +++ b/gallery/src/demos/demo-hui-entities-card.ts @@ -188,7 +188,7 @@ class DemoEntities extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-entity-button-card.js b/gallery/src/demos/demo-hui-entity-button-card.ts similarity index 98% rename from gallery/src/demos/demo-hui-entity-button-card.js rename to gallery/src/demos/demo-hui-entity-button-card.ts index d88627121a..1b201734a4 100644 --- a/gallery/src/demos/demo-hui-entity-button-card.js +++ b/gallery/src/demos/demo-hui-entity-button-card.ts @@ -89,7 +89,7 @@ class DemoEntityButtonEntity extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-entity-filter-card.js b/gallery/src/demos/demo-hui-entity-filter-card.ts similarity index 99% rename from gallery/src/demos/demo-hui-entity-filter-card.js rename to gallery/src/demos/demo-hui-entity-filter-card.ts index 92d8731ff6..3f8f07ce99 100644 --- a/gallery/src/demos/demo-hui-entity-filter-card.js +++ b/gallery/src/demos/demo-hui-entity-filter-card.ts @@ -105,7 +105,7 @@ class DemoFilter extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-gauge-card.js b/gallery/src/demos/demo-hui-gauge-card.ts similarity index 100% rename from gallery/src/demos/demo-hui-gauge-card.js rename to gallery/src/demos/demo-hui-gauge-card.ts diff --git a/gallery/src/demos/demo-hui-glance-card.js b/gallery/src/demos/demo-hui-glance-card.ts similarity index 98% rename from gallery/src/demos/demo-hui-glance-card.js rename to gallery/src/demos/demo-hui-glance-card.ts index 554e84d819..844b7c4656 100644 --- a/gallery/src/demos/demo-hui-glance-card.js +++ b/gallery/src/demos/demo-hui-glance-card.ts @@ -89,10 +89,10 @@ const CONFIGS = [ `, }, { - heading: "Custom column width", + heading: "Custom number of columns", config: ` - type: glance - column_width: calc(100% / 7) + columns: 7 entities: - device_tracker.demo_paulus - media_player.living_room @@ -230,7 +230,7 @@ class DemoPicEntity extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-iframe-card.js b/gallery/src/demos/demo-hui-iframe-card.ts similarity index 100% rename from gallery/src/demos/demo-hui-iframe-card.js rename to gallery/src/demos/demo-hui-iframe-card.ts diff --git a/gallery/src/demos/demo-hui-light-card.js b/gallery/src/demos/demo-hui-light-card.ts similarity index 98% rename from gallery/src/demos/demo-hui-light-card.js rename to gallery/src/demos/demo-hui-light-card.ts index a2d359565d..642f095da6 100644 --- a/gallery/src/demos/demo-hui-light-card.js +++ b/gallery/src/demos/demo-hui-light-card.ts @@ -38,7 +38,7 @@ class DemoLightEntity extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-map-card.js b/gallery/src/demos/demo-hui-map-card.ts similarity index 99% rename from gallery/src/demos/demo-hui-map-card.js rename to gallery/src/demos/demo-hui-map-card.ts index ec6890b2aa..a402d80024 100644 --- a/gallery/src/demos/demo-hui-map-card.js +++ b/gallery/src/demos/demo-hui-map-card.ts @@ -139,7 +139,7 @@ class DemoMap extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-markdown-card.js b/gallery/src/demos/demo-hui-markdown-card.ts similarity index 100% rename from gallery/src/demos/demo-hui-markdown-card.js rename to gallery/src/demos/demo-hui-markdown-card.ts diff --git a/gallery/src/demos/demo-hui-media-player-rows.js b/gallery/src/demos/demo-hui-media-player-rows.ts similarity index 99% rename from gallery/src/demos/demo-hui-media-player-rows.js rename to gallery/src/demos/demo-hui-media-player-rows.ts index d3f2e00700..e657881915 100644 --- a/gallery/src/demos/demo-hui-media-player-rows.js +++ b/gallery/src/demos/demo-hui-media-player-rows.ts @@ -95,7 +95,7 @@ class DemoHuiMediaPlayerRows extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-picture-elements-card.js b/gallery/src/demos/demo-hui-picture-elements-card.ts similarity index 99% rename from gallery/src/demos/demo-hui-picture-elements-card.js rename to gallery/src/demos/demo-hui-picture-elements-card.ts index 9f5ebe90e0..53cb27f585 100644 --- a/gallery/src/demos/demo-hui-picture-elements-card.js +++ b/gallery/src/demos/demo-hui-picture-elements-card.ts @@ -93,7 +93,7 @@ class DemoPicElements extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-picture-entity-card.js b/gallery/src/demos/demo-hui-picture-entity-card.ts similarity index 100% rename from gallery/src/demos/demo-hui-picture-entity-card.js rename to gallery/src/demos/demo-hui-picture-entity-card.ts diff --git a/gallery/src/demos/demo-hui-picture-glance-card.js b/gallery/src/demos/demo-hui-picture-glance-card.ts similarity index 100% rename from gallery/src/demos/demo-hui-picture-glance-card.js rename to gallery/src/demos/demo-hui-picture-glance-card.ts diff --git a/gallery/src/demos/demo-hui-shopping-list-card.js b/gallery/src/demos/demo-hui-shopping-list-card.ts similarity index 98% rename from gallery/src/demos/demo-hui-shopping-list-card.js rename to gallery/src/demos/demo-hui-shopping-list-card.ts index d79e2f839d..b290bd7e9b 100644 --- a/gallery/src/demos/demo-hui-shopping-list-card.js +++ b/gallery/src/demos/demo-hui-shopping-list-card.ts @@ -36,7 +36,7 @@ class DemoShoppingListEntity extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); diff --git a/gallery/src/demos/demo-hui-stack-card.js b/gallery/src/demos/demo-hui-stack-card.ts similarity index 99% rename from gallery/src/demos/demo-hui-stack-card.js rename to gallery/src/demos/demo-hui-stack-card.ts index 1dce40ecfa..257d753d4a 100644 --- a/gallery/src/demos/demo-hui-stack-card.js +++ b/gallery/src/demos/demo-hui-stack-card.ts @@ -104,7 +104,7 @@ class DemoStack extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-hui-thermostat-card.js b/gallery/src/demos/demo-hui-thermostat-card.ts similarity index 99% rename from gallery/src/demos/demo-hui-thermostat-card.js rename to gallery/src/demos/demo-hui-thermostat-card.ts index cbc667c874..740f8c9471 100644 --- a/gallery/src/demos/demo-hui-thermostat-card.js +++ b/gallery/src/demos/demo-hui-thermostat-card.ts @@ -75,7 +75,7 @@ class DemoThermostatEntity extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this.$.demos); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-more-info-light.js b/gallery/src/demos/demo-more-info-light.ts similarity index 82% rename from gallery/src/demos/demo-more-info-light.js rename to gallery/src/demos/demo-more-info-light.ts index 25708dd584..88a5cf7d6b 100644 --- a/gallery/src/demos/demo-more-info-light.js +++ b/gallery/src/demos/demo-more-info-light.ts @@ -8,16 +8,7 @@ import getEntity from "../data/entity"; import provideHass from "../data/provide_hass"; import "../components/demo-more-infos"; - -/* eslint-disable no-unused-vars */ - -const SUPPORT_BRIGHTNESS = 1; -const SUPPORT_COLOR_TEMP = 2; -const SUPPORT_EFFECT = 4; -const SUPPORT_FLASH = 8; -const SUPPORT_COLOR = 16; -const SUPPORT_TRANSITION = 32; -const SUPPORT_WHITE_VALUE = 128; +import { SUPPORT_BRIGHTNESS } from "../../../src/data/light"; const ENTITIES = [ getEntity("light", "bed_light", "on", { @@ -49,7 +40,7 @@ class DemoMoreInfoLight extends PolymerElement { }; } - ready() { + public ready() { super.ready(); const hass = provideHass(this); hass.addEntities(ENTITIES); diff --git a/gallery/src/demos/demo-util-long-press.ts b/gallery/src/demos/demo-util-long-press.ts new file mode 100644 index 0000000000..9e790fed50 --- /dev/null +++ b/gallery/src/demos/demo-util-long-press.ts @@ -0,0 +1,79 @@ +import { html, LitElement } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-button/paper-button"; + +import "../../../src/components/ha-card"; +import { longPress } from "../../../src/panels/lovelace/common/directives/long-press-directive"; + +export class DemoUtilLongPress extends LitElement { + public render(): TemplateResult { + return html` + ${this.renderStyle()} + ${ + [1, 2, 3].map( + () => html` + + + (long) press me! + + + + +
(try pressing and scrolling too!)
+
+ ` + ) + } + `; + } + + private _handleTap(ev: Event) { + this._addValue(ev, "tap"); + } + + private _handleHold(ev: Event) { + this._addValue(ev, "hold"); + } + + private _addValue(ev: Event, value: string) { + const area = (ev.currentTarget as HTMLElement) + .nextElementSibling! as HTMLTextAreaElement; + const now = new Date().toTimeString().split(" ")[0]; + area.value += `${now}: ${value}\n`; + area.scrollTop = area.scrollHeight; + } + + private renderStyle() { + return html` + + `; + } +} + +customElements.define("demo-util-long-press", DemoUtilLongPress); diff --git a/gallery/src/ha-gallery.js b/gallery/src/ha-gallery.js index 73c3e666d6..08af976940 100644 --- a/gallery/src/ha-gallery.js +++ b/gallery/src/ha-gallery.js @@ -11,7 +11,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../../src/managers/notification-manager"; -const DEMOS = require.context("./demos", true, /^(.*\.(js$))[^.]*$/im); +const DEMOS = require.context("./demos", true, /^(.*\.(ts$))[^.]*$/im); const fixPath = (path) => path.substr(2, path.length - 5); @@ -118,6 +118,22 @@ class HaGallery extends PolymerElement { + + +
+

+ Test pages for our utility functions. +

+
+ +
@@ -145,6 +161,10 @@ class HaGallery extends PolymerElement { type: Array, computed: "_computeMoreInfos(_demos)", }, + _utilDemos: { + type: Array, + computed: "_computeUtil(_demos)", + }, }; } @@ -178,7 +198,7 @@ class HaGallery extends PolymerElement { while (root.lastChild) root.removeChild(root.lastChild); if (demo) { - DEMOS(`./${demo}.js`); + DEMOS(`./${demo}.ts`); const el = document.createElement(demo); root.appendChild(el); } @@ -199,6 +219,10 @@ class HaGallery extends PolymerElement { _computeMoreInfos(demos) { return demos.filter((demo) => demo.includes("more-info")); } + + _computeUtil(demos) { + return demos.filter((demo) => demo.includes("util")); + } } customElements.define("ha-gallery", HaGallery); diff --git a/hassio/src/addon-view/hassio-addon-logs.js b/hassio/src/addon-view/hassio-addon-logs.js index 8784689e05..d93be1750e 100644 --- a/hassio/src/addon-view/hassio-addon-logs.js +++ b/hassio/src/addon-view/hassio-addon-logs.js @@ -2,6 +2,7 @@ import "@polymer/paper-button/paper-button"; import "@polymer/paper-card/paper-card"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html"; import "../../../src/resources/ha-style"; @@ -15,10 +16,13 @@ class HassioAddonLogs extends PolymerElement { } pre { overflow-x: auto; + white-space: pre-wrap; + overflow-wrap: break-word; } + ${ANSI_HTML_STYLE} -
[[log]]
+
Refresh
@@ -33,7 +37,6 @@ class HassioAddonLogs extends PolymerElement { type: String, observer: "addonSlugChanged", }, - log: String, }; } @@ -51,8 +54,11 @@ class HassioAddonLogs extends PolymerElement { refresh() { this.hass .callApi("get", `hassio/addons/${this.addonSlug}/logs`) - .then((info) => { - this.log = info; + .then((text) => { + while (this.$.content.lastChild) { + this.$.content.removeChild(this.$.content.lastChild); + } + this.$.content.appendChild(parseTextToColoredPre(text)); }); } } diff --git a/hassio/src/ansi-to-html.js b/hassio/src/ansi-to-html.js new file mode 100644 index 0000000000..d9ab04a690 --- /dev/null +++ b/hassio/src/ansi-to-html.js @@ -0,0 +1,203 @@ +import { html } from "@polymer/polymer/lib/utils/html-tag"; + +export const ANSI_HTML_STYLE = html` + +`; + +export function parseTextToColoredPre(text) { + const pre = document.createElement("pre"); + const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g; + let i = 0; + + const state = { + bold: false, + italic: false, + underline: false, + strikethrough: false, + foregroundColor: null, + backgroundColor: null, + }; + + const addSpan = (content) => { + const span = document.createElement("span"); + if (state.bold) span.classList.add("bold"); + if (state.italic) span.classList.add("italic"); + if (state.underline) span.classList.add("underline"); + if (state.strikethrough) span.classList.add("strikethrough"); + if (state.foregroundColor !== null) + span.classList.add(`fg-${state.foregroundColor}`); + if (state.backgroundColor !== null) + span.classList.add(`bg-${state.backgroundColor}`); + span.appendChild(document.createTextNode(content)); + pre.appendChild(span); + }; + + /* eslint-disable no-cond-assign */ + let match; + while ((match = re.exec(text)) !== null) { + const j = match.index; + addSpan(text.substring(i, j)); + i = j + match[0].length; + + if (match[1] === undefined) continue; + + for (const colorCode of match[1].split(";")) { + switch (parseInt(colorCode)) { + case 0: + // reset + state.bold = false; + state.italic = false; + state.underline = false; + state.strikethrough = false; + state.foregroundColor = null; + state.backgroundColor = null; + break; + case 1: + state.bold = true; + break; + case 3: + state.italic = true; + break; + case 4: + state.underline = true; + break; + case 9: + state.strikethrough = true; + break; + case 22: + state.bold = false; + break; + case 23: + state.italic = false; + break; + case 24: + state.underline = false; + break; + case 29: + state.strikethrough = false; + break; + case 30: + // foreground black + state.foregroundColor = null; + break; + case 31: + state.foregroundColor = "red"; + break; + case 32: + state.foregroundColor = "green"; + break; + case 33: + state.foregroundColor = "yellow"; + break; + case 34: + state.foregroundColor = "blue"; + break; + case 35: + state.foregroundColor = "magenta"; + break; + case 36: + state.foregroundColor = "cyan"; + break; + case 37: + state.foregroundColor = "white"; + break; + case 39: + // foreground reset + state.foregroundColor = null; + break; + case 40: + state.backgroundColor = "black"; + break; + case 41: + state.backgroundColor = "red"; + break; + case 42: + state.backgroundColor = "green"; + break; + case 43: + state.backgroundColor = "yellow"; + break; + case 44: + state.backgroundColor = "blue"; + break; + case 45: + state.backgroundColor = "magenta"; + break; + case 46: + state.backgroundColor = "cyan"; + break; + case 47: + state.backgroundColor = "white"; + break; + case 49: + // background reset + state.backgroundColor = null; + break; + } + } + } + addSpan(text.substring(i)); + + return pre; +} diff --git a/hassio/src/system/hassio-supervisor-log.js b/hassio/src/system/hassio-supervisor-log.js index 26a3a6a078..985cc2372c 100644 --- a/hassio/src/system/hassio-supervisor-log.js +++ b/hassio/src/system/hassio-supervisor-log.js @@ -2,6 +2,7 @@ import "@polymer/paper-button/paper-button"; import "@polymer/paper-card/paper-card"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html"; class HassioSupervisorLog extends PolymerElement { static get template() { @@ -12,12 +13,18 @@ class HassioSupervisorLog extends PolymerElement { } pre { overflow-x: auto; + white-space: pre-wrap; + overflow-wrap: break-word; + } + .fg-green { + color: var(--primary-text-color) !important; } + ${ANSI_HTML_STYLE} -
[[log]]
+
- Refresh + Refresh
`; @@ -26,7 +33,6 @@ class HassioSupervisorLog extends PolymerElement { static get properties() { return { hass: Object, - log: String, }; } @@ -37,16 +43,20 @@ class HassioSupervisorLog extends PolymerElement { loadData() { this.hass.callApi("get", "hassio/supervisor/logs").then( - (info) => { - this.log = info; + (text) => { + while (this.$.content.lastChild) { + this.$.content.removeChild(this.$.content.lastChild); + } + this.$.content.appendChild(parseTextToColoredPre(text)); }, () => { - this.log = "Error fetching logs"; + this.$.content.innerHTML = + 'Error fetching logs'; } ); } - refreshTapped() { + refresh() { this.loadData(); } } diff --git a/package.json b/package.json index 6baea313b5..732dcc03b3 100644 --- a/package.json +++ b/package.json @@ -64,10 +64,10 @@ "@polymer/paper-toggle-button": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1", "@polymer/polymer": "^3.0.5", - "@vaadin/vaadin-combo-box": "^4.2.0-beta2", - "@vaadin/vaadin-date-picker": "^3.3.0", - "@webcomponents/shadycss": "^1.5.2", - "@webcomponents/webcomponentsjs": "^2.1.3", + "@vaadin/vaadin-combo-box": "^4.2.0", + "@vaadin/vaadin-date-picker": "^3.3.1", + "@webcomponents/shadycss": "^1.6.0", + "@webcomponents/webcomponentsjs": "^2.2.0", "chart.js": "~2.7.2", "chartjs-chart-timeline": "^0.2.1", "es6-object-assign": "^1.1.0", @@ -87,6 +87,7 @@ "react-big-calendar": "^0.19.2", "regenerator-runtime": "^0.12.1", "round-slider": "^1.3.2", + "superstruct": "^0.6.0", "unfetch": "^4.0.1", "web-animations-js": "^2.3.1", "xss": "^1.0.3" @@ -152,15 +153,11 @@ "workbox-webpack-plugin": "^3.5.0" }, "resolutions": { - "inherits": "2.0.3", - "samsam": "1.1.3", - "supports-color": "3.1.2", - "type-detect": "1.0.0", "@polymer/polymer": "3.1.0", - "@webcomponents/webcomponentsjs": "2.1.3", - "@webcomponents/shadycss": "^1.5.2", - "@vaadin/vaadin-overlay": "3.2.0-alpha3", - "@vaadin/vaadin-lumo-styles": "1.2.0", + "@webcomponents/webcomponentsjs": "2.2.1", + "@webcomponents/shadycss": "^1.6.0", + "@vaadin/vaadin-overlay": "3.2.2", + "@vaadin/vaadin-lumo-styles": "1.3.0", "fecha": "https://github.com/taylorhakes/fecha/archive/5e8fe08d982647fdb19fb403459838b02647813c.tar.gz", "lit-html": "0.12.0", "@polymer/lit-element": "0.6.2" diff --git a/setup.py b/setup.py index 518c7e1a4d..5c2d1ff135 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20181121.1", + version="20181205.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors", diff --git a/src/cards/ha-plant-card.js b/src/cards/ha-plant-card.js index 2cb91c762d..f1a4fcbbdd 100644 --- a/src/cards/ha-plant-card.js +++ b/src/cards/ha-plant-card.js @@ -103,6 +103,7 @@ class HaPlantCard extends EventsMixin(PolymerElement) { return { hass: Object, stateObj: Object, + config: Object, }; } @@ -118,7 +119,7 @@ class HaPlantCard extends EventsMixin(PolymerElement) { } computeTitle(stateObj) { - return computeStateName(stateObj); + return this.config.name || computeStateName(stateObj); } computeAttributes(data) { diff --git a/src/cards/ha-weather-card.js b/src/cards/ha-weather-card.js index 163663923f..ef9e086c02 100644 --- a/src/cards/ha-weather-card.js +++ b/src/cards/ha-weather-card.js @@ -1,6 +1,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import computeStateName from "../common/entity/compute_state_name"; + import "../components/ha-card"; import "../components/ha-icon"; @@ -106,7 +108,7 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
[[computeState(stateObj.state, localize)]] -
[[stateObj.attributes.friendly_name]]
+
[[computeName(stateObj)]]
@@ -271,6 +273,10 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { return localize(`state.weather.${state}`) || state; } + computeName(stateObj) { + return this.config.name || computeStateName(stateObj); + } + showWeatherIcon(condition) { return condition in this.weatherIcons; } diff --git a/src/common/datetime/relative_time.ts b/src/common/datetime/relative_time.ts index 7daabdc657..ef5a895df0 100644 --- a/src/common/datetime/relative_time.ts +++ b/src/common/datetime/relative_time.ts @@ -10,31 +10,43 @@ const langKey = ["second", "minute", "hour", "day"]; export default function relativeTime( dateObj: Date, - localize: LocalizeFunc + localize: LocalizeFunc, + options: { + compareTime?: Date; + includeTense?: boolean; + } = {} ): string { - let delta = (new Date().getTime() - dateObj.getTime()) / 1000; + const compareTime = options.compareTime || new Date(); + let delta = (compareTime.getTime() - dateObj.getTime()) / 1000; const tense = delta >= 0 ? "past" : "future"; delta = Math.abs(delta); + let timeDesc; + for (let i = 0; i < tests.length; i++) { if (delta < tests[i]) { delta = Math.floor(delta); - const timeDesc = localize( + timeDesc = localize( `ui.components.relative_time.duration.${langKey[i]}`, "count", delta ); - return localize(`ui.components.relative_time.${tense}`, "time", timeDesc); + break; } delta /= tests[i]; } - delta = Math.floor(delta); - const time = localize( - "ui.components.relative_time.duration.week", - "count", - delta - ); - return localize(`ui.components.relative_time.${tense}`, "time", time); + if (timeDesc === undefined) { + delta = Math.floor(delta); + timeDesc = localize( + "ui.components.relative_time.duration.week", + "count", + delta + ); + } + + return options.includeTense === false + ? timeDesc + : localize(`ui.components.relative_time.${tense}`, "time", timeDesc); } diff --git a/src/common/dom/fire_event.js b/src/common/dom/fire_event.ts similarity index 72% rename from src/common/dom/fire_event.js rename to src/common/dom/fire_event.ts index c9e6785b2a..57ecbd02c3 100644 --- a/src/common/dom/fire_event.js +++ b/src/common/dom/fire_event.ts @@ -28,6 +28,17 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +declare global { + // tslint:disable-next-line + interface HASSDomEvents {} +} + +export type ValidHassDomEvent = keyof HASSDomEvents; + +export interface HASSDomEvent extends Event { + detail: T; +} + /** * Dispatches a custom event with an optional detail value. * @@ -35,23 +46,33 @@ * @param {*=} detail Detail value containing event-specific * payload. * @param {{ bubbles: (boolean|undefined), - cancelable: (boolean|undefined), - composed: (boolean|undefined) }=} - * options Object specifying options. These may include: - * `bubbles` (boolean, defaults to `true`), - * `cancelable` (boolean, defaults to false), and - * `node` on which to fire the event (HTMLElement, defaults to `this`). - * @return {Event} The new event that was fired. - */ -export const fireEvent = (node, type, detail, options) => { + * cancelable: (boolean|undefined), + * composed: (boolean|undefined) }=} + * options Object specifying options. These may include: + * `bubbles` (boolean, defaults to `true`), + * `cancelable` (boolean, defaults to false), and + * `node` on which to fire the event (HTMLElement, defaults to `this`). + * @return {Event} The new event that was fired. + */ +export const fireEvent = ( + node: HTMLElement, + type: HassEvent, + detail?: HASSDomEvents[HassEvent], + options?: { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; + } +) => { options = options || {}; + // @ts-ignore detail = detail === null || detail === undefined ? {} : detail; const event = new Event(type, { bubbles: options.bubbles === undefined ? true : options.bubbles, cancelable: Boolean(options.cancelable), composed: options.composed === undefined ? true : options.composed, }); - event.detail = detail; + (event as any).detail = detail; node.dispatchEvent(event); return event; }; diff --git a/src/common/entity/can_toggle_state.ts b/src/common/entity/can_toggle_state.ts index 71a1b5d6c8..d990f363cb 100644 --- a/src/common/entity/can_toggle_state.ts +++ b/src/common/entity/can_toggle_state.ts @@ -2,6 +2,7 @@ import { HassEntity } from "home-assistant-js-websocket"; import canToggleDomain from "./can_toggle_domain"; import computeStateDomain from "./compute_state_domain"; import { HomeAssistant } from "../../types"; +import { supportsFeature } from "./supports-feature"; export default function canToggleState( hass: HomeAssistant, @@ -12,8 +13,7 @@ export default function canToggleState( return stateObj.state === "on" || stateObj.state === "off"; } if (domain === "climate") { - // tslint:disable-next-line - return (stateObj.attributes.supported_features! & 4096) !== 0; + return supportsFeature(stateObj, 4096); } return canToggleDomain(hass, domain); diff --git a/src/common/entity/extract_views.ts b/src/common/entity/extract_views.ts index bcc7084646..d5679e5518 100644 --- a/src/common/entity/extract_views.ts +++ b/src/common/entity/extract_views.ts @@ -1,14 +1,15 @@ -import { HassEntities, HassEntity } from "home-assistant-js-websocket"; +import { HassEntities } from "home-assistant-js-websocket"; import { DEFAULT_VIEW_ENTITY_ID } from "../const"; +import { GroupEntity } from "../../types"; // Return an ordered array of available views -export default function extractViews(entities: HassEntities): HassEntity[] { - const views: HassEntity[] = []; +export default function extractViews(entities: HassEntities): GroupEntity[] { + const views: GroupEntity[] = []; Object.keys(entities).forEach((entityId) => { const entity = entities[entityId]; if (entity.attributes.view) { - views.push(entity); + views.push(entity as GroupEntity); } }); diff --git a/src/common/entity/feature_class_names.ts b/src/common/entity/feature_class_names.ts index d2d0cac26e..768e025a93 100644 --- a/src/common/entity/feature_class_names.ts +++ b/src/common/entity/feature_class_names.ts @@ -1,4 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; +import { supportsFeature } from "./supports-feature"; // Expects classNames to be an object mapping feature-bit -> className export default function featureClassNames( @@ -9,12 +10,9 @@ export default function featureClassNames( return ""; } - const features = stateObj.attributes.supported_features; - return Object.keys(classNames) .map((feature) => - // tslint:disable-next-line - (features & Number(feature)) !== 0 ? classNames[feature] : "" + supportsFeature(stateObj, Number(feature)) ? classNames[feature] : "" ) .filter((attr) => attr !== "") .join(" "); diff --git a/src/common/entity/get_view_entities.ts b/src/common/entity/get_view_entities.ts index 62aa7e5d89..c3d249cfcd 100644 --- a/src/common/entity/get_view_entities.ts +++ b/src/common/entity/get_view_entities.ts @@ -8,7 +8,7 @@ import { GroupEntity } from "../../types"; export default function getViewEntities( entities: HassEntities, view: GroupEntity -) { +): HassEntities { const viewEntities = {}; view.attributes.entity_id.forEach((entityId) => { diff --git a/src/common/entity/split_by_groups.ts b/src/common/entity/split_by_groups.ts index a894472d8c..aed1ab7f0e 100644 --- a/src/common/entity/split_by_groups.ts +++ b/src/common/entity/split_by_groups.ts @@ -1,18 +1,19 @@ import computeDomain from "./compute_domain"; -import { HassEntity, HassEntities } from "home-assistant-js-websocket"; +import { HassEntities } from "home-assistant-js-websocket"; +import { GroupEntity } from "../../types"; // Split a collection into a list of groups and a 'rest' list of ungrouped // entities. // Returns { groups: [], ungrouped: {} } export default function splitByGroups(entities: HassEntities) { - const groups: HassEntity[] = []; + const groups: GroupEntity[] = []; const ungrouped: HassEntities = {}; Object.keys(entities).forEach((entityId) => { const entity = entities[entityId]; if (computeDomain(entityId) === "group") { - groups.push(entity); + groups.push(entity as GroupEntity); } else { ungrouped[entityId] = entity; } diff --git a/src/common/entity/supports-feature.ts b/src/common/entity/supports-feature.ts new file mode 100644 index 0000000000..9aa833c035 --- /dev/null +++ b/src/common/entity/supports-feature.ts @@ -0,0 +1,9 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +export const supportsFeature = ( + stateObj: HassEntity, + feature: number +): boolean => { + // tslint:disable-next-line:no-bitwise + return (stateObj.attributes.supported_features! & feature) !== 0; +}; diff --git a/src/common/util/compute_rtl.ts b/src/common/util/compute_rtl.ts new file mode 100644 index 0000000000..62be0a1c7e --- /dev/null +++ b/src/common/util/compute_rtl.ts @@ -0,0 +1,9 @@ +import { HomeAssistant } from "../../types"; + +export function computeRTL(hass: HomeAssistant) { + const lang = hass.language || "en"; + if (hass.translationMetadata.translations[lang]) { + return hass.translationMetadata.translations[lang].isRTL || false; + } + return false; +} diff --git a/src/common/util/uid.ts b/src/common/util/uid.ts new file mode 100644 index 0000000000..f52a03e0cd --- /dev/null +++ b/src/common/util/uid.ts @@ -0,0 +1,9 @@ +function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); +} + +export function uid() { + return s4() + s4() + s4() + s4() + s4(); +} diff --git a/src/components/entity/state-info.js b/src/components/entity/state-info.js index 64b67da2e5..d2859d15d5 100644 --- a/src/components/entity/state-info.js +++ b/src/components/entity/state-info.js @@ -4,6 +4,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../ha-relative-time"; import "./state-badge"; import computeStateName from "../../common/entity/compute_state_name"; +import { computeRTL } from "../../common/util/compute_rtl"; class StateInfo extends PolymerElement { static get template() { @@ -25,10 +26,20 @@ class StateInfo extends PolymerElement { float: left; } + :host([rtl]) state-badge { + float: right; + } + .info { margin-left: 56px; } + :host([rtl]) .info { + margin-right: 56px; + margin-left: 0; + text-align: right; + } + .name { @apply --paper-font-common-nowrap; color: var(--primary-text-color); @@ -87,12 +98,21 @@ class StateInfo extends PolymerElement { hass: Object, stateObj: Object, inDialog: Boolean, + rtl: { + type: Boolean, + reflectToAttribute: true, + computed: "computeRTL(hass)", + }, }; } computeStateName(stateObj) { return computeStateName(stateObj); } + + computeRTL(hass) { + return computeRTL(hass); + } } customElements.define("state-info", StateInfo); diff --git a/src/components/ha-card.js b/src/components/ha-card.js index 36cd9f760f..f497d69d02 100644 --- a/src/components/ha-card.js +++ b/src/components/ha-card.js @@ -12,6 +12,7 @@ class HaCard extends PolymerElement { border-radius: 2px; transition: all 0.3s ease-out; background-color: var(--paper-card-background-color, white); + color: var(--primary-text-color); } .header { @apply --paper-font-headline; diff --git a/src/components/ha-cards.js b/src/components/ha-cards.js index 587b41a0d7..c5ecbac486 100644 --- a/src/components/ha-cards.js +++ b/src/components/ha-cards.js @@ -123,7 +123,7 @@ class HaCards extends PolymerElement {
-