diff --git a/package.json b/package.json index 1e6bc6abf7..82f4d700b1 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "version": "1.0.0", "scripts": { "build": "script/build_frontend", - "lint": "eslint src hassio/src gallery/src test-mocha && tslint -c tslint.json 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'test-mocha/**/*.ts' && polymer lint && tsc", - "mocha": "node_modules/.bin/mocha --opts test-mocha/mocha.opts", + "lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'test-mocha/**/*.ts' && polymer lint && tsc", + "mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts", "test": "npm run lint && npm run mocha", "docker_build": "sh ./script/docker_run.sh build $npm_package_version", "bash": "sh ./script/docker_run.sh bash $npm_package_version" @@ -100,10 +100,12 @@ "@babel/preset-env": "^7.1.0", "@babel/preset-typescript": "^7.1.0", "@gfx/zopfli": "^1.0.9", + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.5", "babel-eslint": "^10", "babel-loader": "^8.0.4", "babel-minify-webpack-plugin": "^0.3.1", - "chai": "^4.1.2", + "chai": "^4.2.0", "compression-webpack-plugin": "^2.0.0", "copy-webpack-plugin": "^4.5.2", "del": "^3.0.0", @@ -126,7 +128,6 @@ "husky": "^1.1.0", "lint-staged": "^8.0.2", "merge-stream": "^1.0.1", - "mocha": "^5.2.0", "parse5": "^5.1.0", "polymer-analyzer": "^3.1.2", "polymer-bundler": "^4.0.2", @@ -135,7 +136,8 @@ "raw-loader": "^0.5.1", "reify": "^0.18.1", "require-dir": "^1.0.0", - "sinon": "^7.1.0", + "sinon": "^7.1.1", + "ts-mocha": "^2.0.0", "tslint": "^5.11.0", "tslint-config-prettier": "^1.15.0", "tslint-eslint-rules": "^5.4.0", diff --git a/src/common/config/is_component_loaded.js b/src/common/config/is_component_loaded.js deleted file mode 100644 index 318eb56936..0000000000 --- a/src/common/config/is_component_loaded.js +++ /dev/null @@ -1,4 +0,0 @@ -/** Return if a component is loaded. */ -export default function isComponentLoaded(hass, component) { - return hass && hass.config.components.indexOf(component) !== -1; -} diff --git a/src/common/config/is_component_loaded.ts b/src/common/config/is_component_loaded.ts new file mode 100644 index 0000000000..c8535cda6b --- /dev/null +++ b/src/common/config/is_component_loaded.ts @@ -0,0 +1,9 @@ +import { HomeAssistant } from "../../types"; + +/** Return if a component is loaded. */ +export default function isComponentLoaded( + hass: HomeAssistant, + component: string +): boolean { + return hass && hass.config.components.indexOf(component) !== -1; +} diff --git a/src/common/config/is_pwa.js b/src/common/config/is_pwa.ts similarity index 75% rename from src/common/config/is_pwa.js rename to src/common/config/is_pwa.ts index c2a4bcbde6..a7fa4f4376 100644 --- a/src/common/config/is_pwa.js +++ b/src/common/config/is_pwa.ts @@ -1,4 +1,4 @@ /** Return if the displaymode is in standalone mode (PWA). */ -export default function isPwa() { +export default function isPwa(): boolean { return window.matchMedia("(display-mode: standalone)").matches; } diff --git a/src/common/config/location_name.js b/src/common/config/location_name.js deleted file mode 100644 index 83d8fc32a4..0000000000 --- a/src/common/config/location_name.js +++ /dev/null @@ -1,4 +0,0 @@ -/** Get the location name from a hass object. */ -export default function computeLocationName(hass) { - return hass && hass.config.location_name; -} diff --git a/src/common/config/location_name.ts b/src/common/config/location_name.ts new file mode 100644 index 0000000000..d198951b24 --- /dev/null +++ b/src/common/config/location_name.ts @@ -0,0 +1,6 @@ +import { HomeAssistant } from "../../types"; + +/** Get the location name from a hass object. */ +export default function computeLocationName(hass: HomeAssistant): string { + return hass && hass.config.location_name; +} diff --git a/src/common/const.js b/src/common/const.ts similarity index 100% rename from src/common/const.js rename to src/common/const.ts diff --git a/src/common/datetime/duration_to_seconds.js b/src/common/datetime/duration_to_seconds.ts similarity index 59% rename from src/common/datetime/duration_to_seconds.js rename to src/common/datetime/duration_to_seconds.ts index bdd4a006a9..f579c57b88 100644 --- a/src/common/datetime/duration_to_seconds.js +++ b/src/common/datetime/duration_to_seconds.ts @@ -1,4 +1,4 @@ -export default function durationToSeconds(duration) { +export default function durationToSeconds(duration: string): number { const parts = duration.split(":").map(Number); return parts[0] * 3600 + parts[1] * 60 + parts[2]; } diff --git a/src/common/datetime/format_date.js b/src/common/datetime/format_date.ts similarity index 70% rename from src/common/datetime/format_date.js rename to src/common/datetime/format_date.ts index fac7fb1ce9..09cd0223e2 100644 --- a/src/common/datetime/format_date.js +++ b/src/common/datetime/format_date.ts @@ -1,4 +1,4 @@ -import fecha from "fecha"; +import * as fecha from "fecha"; // Check for support of native locale string options function toLocaleDateStringSupportsOptions() { @@ -11,11 +11,10 @@ function toLocaleDateStringSupportsOptions() { } export default (toLocaleDateStringSupportsOptions() - ? (dateObj, locales) => + ? (dateObj: Date, locales: string) => dateObj.toLocaleDateString(locales, { year: "numeric", month: "long", day: "numeric", }) - : // eslint-disable-next-line no-unused-vars - (dateObj, locales) => fecha.format(dateObj, "mediumDate")); + : (dateObj: Date) => fecha.format(dateObj, "mediumDate")); diff --git a/src/common/datetime/format_date_time.js b/src/common/datetime/format_date_time.ts similarity index 72% rename from src/common/datetime/format_date_time.js rename to src/common/datetime/format_date_time.ts index bc8d54cd47..3421a5d291 100644 --- a/src/common/datetime/format_date_time.js +++ b/src/common/datetime/format_date_time.ts @@ -1,4 +1,4 @@ -import fecha from "fecha"; +import * as fecha from "fecha"; // Check for support of native locale string options function toLocaleStringSupportsOptions() { @@ -11,7 +11,7 @@ function toLocaleStringSupportsOptions() { } export default (toLocaleStringSupportsOptions() - ? (dateObj, locales) => + ? (dateObj: Date, locales: string) => dateObj.toLocaleString(locales, { year: "numeric", month: "long", @@ -19,5 +19,4 @@ export default (toLocaleStringSupportsOptions() hour: "numeric", minute: "2-digit", }) - : // eslint-disable-next-line no-unused-vars - (dateObj, locales) => fecha.format(dateObj, "haDateTime")); + : (dateObj: Date) => fecha.format(dateObj, "haDateTime")); diff --git a/src/common/datetime/format_time.js b/src/common/datetime/format_time.ts similarity index 70% rename from src/common/datetime/format_time.js rename to src/common/datetime/format_time.ts index dcf7d32d7d..e5abe800b6 100644 --- a/src/common/datetime/format_time.js +++ b/src/common/datetime/format_time.ts @@ -1,4 +1,4 @@ -import fecha from "fecha"; +import * as fecha from "fecha"; // Check for support of native locale string options function toLocaleTimeStringSupportsOptions() { @@ -11,10 +11,9 @@ function toLocaleTimeStringSupportsOptions() { } export default (toLocaleTimeStringSupportsOptions() - ? (dateObj, locales) => + ? (dateObj: Date, locales: string) => dateObj.toLocaleTimeString(locales, { hour: "numeric", minute: "2-digit", }) - : // eslint-disable-next-line no-unused-vars - (dateObj, locales) => fecha.format(dateObj, "shortTime")); + : (dateObj: Date) => fecha.format(dateObj, "shortTime")); diff --git a/src/common/datetime/relative_time.js b/src/common/datetime/relative_time.js deleted file mode 100644 index c76d071c22..0000000000 --- a/src/common/datetime/relative_time.js +++ /dev/null @@ -1,33 +0,0 @@ -/** Calculate a string representing a date object as relative time from now. - * - * Example output: 5 minutes ago, in 3 days. - */ -const tests = [60, "second", 60, "minute", 24, "hour", 7, "day"]; - -export default function relativeTime(dateObj, localize) { - let delta = (new Date() - dateObj) / 1000; - const tense = delta >= 0 ? "past" : "future"; - delta = Math.abs(delta); - - for (let i = 0; i < tests.length; i += 2) { - if (delta < tests[i]) { - delta = Math.floor(delta); - const time = localize( - `ui.components.relative_time.duration.${tests[i + 1]}`, - "count", - delta - ); - return localize(`ui.components.relative_time.${tense}`, "time", time); - } - - 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); -} diff --git a/src/common/datetime/relative_time.ts b/src/common/datetime/relative_time.ts new file mode 100644 index 0000000000..7daabdc657 --- /dev/null +++ b/src/common/datetime/relative_time.ts @@ -0,0 +1,40 @@ +import { LocalizeFunc } from "../../mixins/localize-base-mixin"; + +/** + * Calculate a string representing a date object as relative time from now. + * + * Example output: 5 minutes ago, in 3 days. + */ +const tests = [60, 60, 24, 7]; +const langKey = ["second", "minute", "hour", "day"]; + +export default function relativeTime( + dateObj: Date, + localize: LocalizeFunc +): string { + let delta = (new Date().getTime() - dateObj.getTime()) / 1000; + const tense = delta >= 0 ? "past" : "future"; + delta = Math.abs(delta); + + for (let i = 0; i < tests.length; i++) { + if (delta < tests[i]) { + delta = Math.floor(delta); + const timeDesc = localize( + `ui.components.relative_time.duration.${langKey[i]}`, + "count", + delta + ); + return localize(`ui.components.relative_time.${tense}`, "time", timeDesc); + } + + 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); +} diff --git a/src/common/datetime/seconds_to_duration.js b/src/common/datetime/seconds_to_duration.ts similarity index 71% rename from src/common/datetime/seconds_to_duration.js rename to src/common/datetime/seconds_to_duration.ts index a256c14632..4874137803 100644 --- a/src/common/datetime/seconds_to_duration.js +++ b/src/common/datetime/seconds_to_duration.ts @@ -1,6 +1,6 @@ -const leftPad = (number) => (number < 10 ? `0${number}` : number); +const leftPad = (num: number) => (num < 10 ? `0${num}` : num); -export default function secondsToDuration(d) { +export default function secondsToDuration(d: number) { const h = Math.floor(d / 3600); const m = Math.floor((d % 3600) / 60); const s = Math.floor((d % 3600) % 60); diff --git a/src/common/empty_image_base64.js b/src/common/empty_image_base64.ts similarity index 100% rename from src/common/empty_image_base64.js rename to src/common/empty_image_base64.ts diff --git a/src/common/entity/attribute_class_names.js b/src/common/entity/attribute_class_names.js deleted file mode 100644 index cde81825ac..0000000000 --- a/src/common/entity/attribute_class_names.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function attributeClassNames(stateObj, attributes) { - if (!stateObj) return ""; - return attributes - .map(function(attribute) { - return attribute in stateObj.attributes ? "has-" + attribute : ""; - }) - .filter((attr) => attr !== "") - .join(" "); -} diff --git a/src/common/entity/attribute_class_names.ts b/src/common/entity/attribute_class_names.ts new file mode 100644 index 0000000000..28d06f53fa --- /dev/null +++ b/src/common/entity/attribute_class_names.ts @@ -0,0 +1,17 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +export default function attributeClassNames( + stateObj: HassEntity, + attributes: string[] +): string { + if (!stateObj) { + return ""; + } + return attributes + .map( + (attribute) => + attribute in stateObj.attributes ? "has-" + attribute : "" + ) + .filter((attr) => attr !== "") + .join(" "); +} diff --git a/src/common/entity/binary_sensor_icon.js b/src/common/entity/binary_sensor_icon.ts similarity index 90% rename from src/common/entity/binary_sensor_icon.js rename to src/common/entity/binary_sensor_icon.ts index e617f10f6d..365aba566b 100644 --- a/src/common/entity/binary_sensor_icon.js +++ b/src/common/entity/binary_sensor_icon.ts @@ -1,7 +1,9 @@ +import { HassEntity } from "home-assistant-js-websocket"; + /** Return an icon representing a binary sensor state. */ -export default function binarySensorIcon(state) { - var activated = state.state && state.state === "off"; +export default function binarySensorIcon(state: HassEntity) { + const activated = state.state && state.state === "off"; switch (state.attributes.device_class) { case "battery": return activated ? "hass:battery" : "hass:battery-outline"; diff --git a/src/common/entity/can_toggle_domain.js b/src/common/entity/can_toggle_domain.ts similarity index 66% rename from src/common/entity/can_toggle_domain.js rename to src/common/entity/can_toggle_domain.ts index 6b284e5f17..f1a0bc47b3 100644 --- a/src/common/entity/can_toggle_domain.js +++ b/src/common/entity/can_toggle_domain.ts @@ -1,4 +1,6 @@ -export default function canToggleDomain(hass, domain) { +import { HomeAssistant } from "../../types"; + +export default function canToggleDomain(hass: HomeAssistant, domain: string) { const services = hass.services[domain]; if (!services) { return false; diff --git a/src/common/entity/can_toggle_state.js b/src/common/entity/can_toggle_state.ts similarity index 52% rename from src/common/entity/can_toggle_state.js rename to src/common/entity/can_toggle_state.ts index 8b7a1eaee0..71a1b5d6c8 100644 --- a/src/common/entity/can_toggle_state.js +++ b/src/common/entity/can_toggle_state.ts @@ -1,13 +1,19 @@ +import { HassEntity } from "home-assistant-js-websocket"; import canToggleDomain from "./can_toggle_domain"; import computeStateDomain from "./compute_state_domain"; +import { HomeAssistant } from "../../types"; -export default function canToggleState(hass, stateObj) { +export default function canToggleState( + hass: HomeAssistant, + stateObj: HassEntity +) { const domain = computeStateDomain(stateObj); if (domain === "group") { return stateObj.state === "on" || stateObj.state === "off"; } if (domain === "climate") { - return !!((stateObj.attributes || {}).supported_features & 4096); + // tslint:disable-next-line + return (stateObj.attributes.supported_features! & 4096) !== 0; } return canToggleDomain(hass, domain); diff --git a/src/common/entity/compute_domain.js b/src/common/entity/compute_domain.js deleted file mode 100644 index b9d6fecaf7..0000000000 --- a/src/common/entity/compute_domain.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function computeDomain(entityId) { - return entityId.substr(0, entityId.indexOf(".")); -} diff --git a/src/common/entity/compute_domain.ts b/src/common/entity/compute_domain.ts new file mode 100644 index 0000000000..47eeb9e890 --- /dev/null +++ b/src/common/entity/compute_domain.ts @@ -0,0 +1,3 @@ +export default function computeDomain(entityId: string): string { + return entityId.substr(0, entityId.indexOf(".")); +} diff --git a/src/common/entity/compute_object_id.js b/src/common/entity/compute_object_id.ts similarity index 58% rename from src/common/entity/compute_object_id.js rename to src/common/entity/compute_object_id.ts index 85888b5c79..865ab6254d 100644 --- a/src/common/entity/compute_object_id.js +++ b/src/common/entity/compute_object_id.ts @@ -1,4 +1,4 @@ /** Compute the object ID of a state. */ -export default function computeObjectId(entityId) { +export default function computeObjectId(entityId: string): string { return entityId.substr(entityId.indexOf(".") + 1); } diff --git a/src/common/entity/compute_state_display.js b/src/common/entity/compute_state_display.js deleted file mode 100644 index 56f5d28c48..0000000000 --- a/src/common/entity/compute_state_display.js +++ /dev/null @@ -1,84 +0,0 @@ -import computeStateDomain from "./compute_state_domain"; -import formatDateTime from "../datetime/format_date_time"; -import formatDate from "../datetime/format_date"; -import formatTime from "../datetime/format_time"; - -export default function computeStateDisplay(localize, stateObj, language) { - if (!stateObj._stateDisplay) { - const domain = computeStateDomain(stateObj); - if (domain === "binary_sensor") { - // Try device class translation, then default binary sensor translation - if (stateObj.attributes.device_class) { - stateObj._stateDisplay = localize( - `state.${domain}.${stateObj.attributes.device_class}.${ - stateObj.state - }` - ); - } - if (!stateObj._stateDisplay) { - stateObj._stateDisplay = localize( - `state.${domain}.default.${stateObj.state}` - ); - } - } else if ( - stateObj.attributes.unit_of_measurement && - !["unknown", "unavailable"].includes(stateObj.state) - ) { - stateObj._stateDisplay = - stateObj.state + " " + stateObj.attributes.unit_of_measurement; - } else if (domain === "input_datetime") { - let date; - if (!stateObj.attributes.has_time) { - date = new Date( - stateObj.attributes.year, - stateObj.attributes.month - 1, - stateObj.attributes.day - ); - stateObj._stateDisplay = formatDate(date, language); - } else if (!stateObj.attributes.has_date) { - const now = new Date(); - date = new Date( - // Due to bugs.chromium.org/p/chromium/issues/detail?id=797548 - // don't use artificial 1970 year. - now.getFullYear(), - now.getMonth(), - now.getDay(), - stateObj.attributes.hour, - stateObj.attributes.minute - ); - stateObj._stateDisplay = formatTime(date, language); - } else { - date = new Date( - stateObj.attributes.year, - stateObj.attributes.month - 1, - stateObj.attributes.day, - stateObj.attributes.hour, - stateObj.attributes.minute - ); - stateObj._stateDisplay = formatDateTime(date, language); - } - } else if (domain === "zwave") { - if (["initializing", "dead"].includes(stateObj.state)) { - stateObj._stateDisplay = localize( - `state.zwave.query_stage.${stateObj.state}`, - "query_stage", - stateObj.attributes.query_stage - ); - } else { - stateObj._stateDisplay = localize( - `state.zwave.default.${stateObj.state}` - ); - } - } else { - stateObj._stateDisplay = localize(`state.${domain}.${stateObj.state}`); - } - // Fall back to default, component backend translation, or raw state if nothing else matches. - stateObj._stateDisplay = - stateObj._stateDisplay || - localize(`state.default.${stateObj.state}`) || - localize(`component.${domain}.state.${stateObj.state}`) || - stateObj.state; - } - - return stateObj._stateDisplay; -} diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts new file mode 100644 index 0000000000..83fb1f5578 --- /dev/null +++ b/src/common/entity/compute_state_display.ts @@ -0,0 +1,91 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import computeStateDomain from "./compute_state_domain"; +import formatDateTime from "../datetime/format_date_time"; +import formatDate from "../datetime/format_date"; +import formatTime from "../datetime/format_time"; +import { LocalizeFunc } from "../../mixins/localize-base-mixin"; + +type CachedDisplayEntity = HassEntity & { + _stateDisplay?: string; +}; + +export default function computeStateDisplay( + localize: LocalizeFunc, + stateObj: HassEntity, + language: string +) { + const state = stateObj as CachedDisplayEntity; + if (!state._stateDisplay) { + const domain = computeStateDomain(state); + if (domain === "binary_sensor") { + // Try device class translation, then default binary sensor translation + if (state.attributes.device_class) { + state._stateDisplay = localize( + `state.${domain}.${state.attributes.device_class}.${state.state}` + ); + } + if (!state._stateDisplay) { + state._stateDisplay = localize( + `state.${domain}.default.${state.state}` + ); + } + } else if ( + state.attributes.unit_of_measurement && + !["unknown", "unavailable"].includes(state.state) + ) { + state._stateDisplay = + state.state + " " + state.attributes.unit_of_measurement; + } else if (domain === "input_datetime") { + let date; + if (!state.attributes.has_time) { + date = new Date( + state.attributes.year, + state.attributes.month - 1, + state.attributes.day + ); + state._stateDisplay = formatDate(date, language); + } else if (!state.attributes.has_date) { + const now = new Date(); + date = new Date( + // Due to bugs.chromium.org/p/chromium/issues/detail?id=797548 + // don't use artificial 1970 year. + now.getFullYear(), + now.getMonth(), + now.getDay(), + state.attributes.hour, + state.attributes.minute + ); + state._stateDisplay = formatTime(date, language); + } else { + date = new Date( + state.attributes.year, + state.attributes.month - 1, + state.attributes.day, + state.attributes.hour, + state.attributes.minute + ); + state._stateDisplay = formatDateTime(date, language); + } + } else if (domain === "zwave") { + if (["initializing", "dead"].includes(state.state)) { + state._stateDisplay = localize( + `state.zwave.query_stage.${state.state}`, + "query_stage", + state.attributes.query_stage + ); + } else { + state._stateDisplay = localize(`state.zwave.default.${state.state}`); + } + } else { + state._stateDisplay = localize(`state.${domain}.${state.state}`); + } + // Fall back to default, component backend translation, or raw state if nothing else matches. + state._stateDisplay = + state._stateDisplay || + localize(`state.default.${state.state}`) || + localize(`component.${domain}.state.${state.state}`) || + state.state; + } + + return state._stateDisplay; +} diff --git a/src/common/entity/compute_state_domain.js b/src/common/entity/compute_state_domain.js deleted file mode 100644 index 492b700eaa..0000000000 --- a/src/common/entity/compute_state_domain.js +++ /dev/null @@ -1,5 +0,0 @@ -import computeDomain from "./compute_domain"; - -export default function computeStateDomain(stateObj) { - return computeDomain(stateObj.entity_id); -} diff --git a/src/common/entity/compute_state_domain.ts b/src/common/entity/compute_state_domain.ts new file mode 100644 index 0000000000..1ef02f4e8f --- /dev/null +++ b/src/common/entity/compute_state_domain.ts @@ -0,0 +1,6 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import computeDomain from "./compute_domain"; + +export default function computeStateDomain(stateObj: HassEntity) { + return computeDomain(stateObj.entity_id); +} diff --git a/src/common/entity/compute_state_name.js b/src/common/entity/compute_state_name.js deleted file mode 100644 index 0aa20d7ccf..0000000000 --- a/src/common/entity/compute_state_name.js +++ /dev/null @@ -1,11 +0,0 @@ -import computeObjectId from "./compute_object_id"; - -export default function computeStateName(stateObj) { - if (stateObj._entityDisplay === undefined) { - stateObj._entityDisplay = - stateObj.attributes.friendly_name || - computeObjectId(stateObj.entity_id).replace(/_/g, " "); - } - - return stateObj._entityDisplay; -} diff --git a/src/common/entity/compute_state_name.ts b/src/common/entity/compute_state_name.ts new file mode 100644 index 0000000000..c20bde78b7 --- /dev/null +++ b/src/common/entity/compute_state_name.ts @@ -0,0 +1,18 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import computeObjectId from "./compute_object_id"; + +type CachedDisplayEntity = HassEntity & { + _entityDisplay?: string; +}; + +export default function computeStateName(stateObj: HassEntity) { + const state = stateObj as CachedDisplayEntity; + + if (state._entityDisplay === undefined) { + state._entityDisplay = + state.attributes.friendly_name || + computeObjectId(state.entity_id).replace(/_/g, " "); + } + + return state._entityDisplay; +} diff --git a/src/common/entity/cover_icon.js b/src/common/entity/cover_icon.ts similarity index 62% rename from src/common/entity/cover_icon.js rename to src/common/entity/cover_icon.ts index 78876025b4..88fe03f774 100644 --- a/src/common/entity/cover_icon.js +++ b/src/common/entity/cover_icon.ts @@ -1,8 +1,9 @@ /** Return an icon representing a cover state. */ +import { HassEntity } from "home-assistant-js-websocket"; import domainIcon from "./domain_icon"; -export default function coverIcon(state) { - var open = state.state && state.state !== "closed"; +export default function coverIcon(state: HassEntity): string { + const open = state.state !== "closed"; switch (state.attributes.device_class) { case "garage": return open ? "hass:garage-open" : "hass:garage"; diff --git a/src/common/entity/domain_icon.js b/src/common/entity/domain_icon.ts similarity index 95% rename from src/common/entity/domain_icon.js rename to src/common/entity/domain_icon.ts index a50fcb943e..5c6ac2a181 100644 --- a/src/common/entity/domain_icon.js +++ b/src/common/entity/domain_icon.ts @@ -44,7 +44,7 @@ const fixedIcons = { weblink: "hass:open-in-new", }; -export default function domainIcon(domain, state) { +export default function domainIcon(domain: string, state?: string): string { if (domain in fixedIcons) { return fixedIcons[domain]; } @@ -93,11 +93,10 @@ export default function domainIcon(domain, state) { } default: - /* eslint-disable no-console */ + // tslint:disable-next-line console.warn( "Unable to find icon for domain " + domain + " (" + state + ")" ); - /* eslint-enable no-console */ return DEFAULT_DOMAIN_ICON; } } diff --git a/src/common/entity/extract_views.js b/src/common/entity/extract_views.ts similarity index 74% rename from src/common/entity/extract_views.js rename to src/common/entity/extract_views.ts index c3b7949911..bcc7084646 100644 --- a/src/common/entity/extract_views.js +++ b/src/common/entity/extract_views.ts @@ -1,8 +1,9 @@ +import { HassEntities, HassEntity } from "home-assistant-js-websocket"; import { DEFAULT_VIEW_ENTITY_ID } from "../const"; // Return an ordered array of available views -export default function extractViews(entities) { - const views = []; +export default function extractViews(entities: HassEntities): HassEntity[] { + const views: HassEntity[] = []; Object.keys(entities).forEach((entityId) => { const entity = entities[entityId]; diff --git a/src/common/entity/feature_class_names.js b/src/common/entity/feature_class_names.js deleted file mode 100644 index 93766e549b..0000000000 --- a/src/common/entity/feature_class_names.js +++ /dev/null @@ -1,11 +0,0 @@ -// Expects classNames to be an object mapping feature-bit -> className -export default function featureClassNames(stateObj, classNames) { - if (!stateObj || !stateObj.attributes.supported_features) return ""; - - const features = stateObj.attributes.supported_features; - - return Object.keys(classNames) - .map((feature) => ((features & feature) !== 0 ? classNames[feature] : "")) - .filter((attr) => attr !== "") - .join(" "); -} diff --git a/src/common/entity/feature_class_names.ts b/src/common/entity/feature_class_names.ts new file mode 100644 index 0000000000..4cafc68b86 --- /dev/null +++ b/src/common/entity/feature_class_names.ts @@ -0,0 +1,22 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +// Expects classNames to be an object mapping feature-bit -> className +export default function featureClassNames( + stateObj: HassEntity, + classNames: { [feature: number]: string } +) { + if (!stateObj || !stateObj.attributes.supported_features) { + return ""; + } + + const features = stateObj.attributes.supported_features; + + return Object.keys(classNames) + .map( + (feature) => + // tslint:disable-next-line + (features & Number(feature)) !== 0 ? classNames[feature] : "" + ) + .filter((attr) => attr !== "") + .join(" "); +} diff --git a/src/common/entity/get_group_entities.js b/src/common/entity/get_group_entities.ts similarity index 51% rename from src/common/entity/get_group_entities.js rename to src/common/entity/get_group_entities.ts index 5e034a9df8..b77353e0c1 100644 --- a/src/common/entity/get_group_entities.js +++ b/src/common/entity/get_group_entities.ts @@ -1,4 +1,10 @@ -export default function getGroupEntities(entities, group) { +import { HassEntities } from "home-assistant-js-websocket"; +import { GroupEntity } from "../../types"; + +export default function getGroupEntities( + entities: HassEntities, + group: GroupEntity +) { const result = {}; group.attributes.entity_id.forEach((entityId) => { diff --git a/src/common/entity/get_view_entities.js b/src/common/entity/get_view_entities.ts similarity index 73% rename from src/common/entity/get_view_entities.js rename to src/common/entity/get_view_entities.ts index 00b21ee30e..62aa7e5d89 100644 --- a/src/common/entity/get_view_entities.js +++ b/src/common/entity/get_view_entities.ts @@ -1,9 +1,14 @@ +import { HassEntities } from "home-assistant-js-websocket"; import computeDomain from "./compute_domain"; import getGroupEntities from "./get_group_entities"; +import { GroupEntity } from "../../types"; // Return an object containing all entities that the view will show // including embedded groups. -export default function getViewEntities(entities, view) { +export default function getViewEntities( + entities: HassEntities, + view: GroupEntity +) { const viewEntities = {}; view.attributes.entity_id.forEach((entityId) => { @@ -13,7 +18,7 @@ export default function getViewEntities(entities, view) { viewEntities[entity.entity_id] = entity; if (computeDomain(entity.entity_id) === "group") { - const groupEntities = getGroupEntities(entities, entity); + const groupEntities = getGroupEntities(entities, entity as GroupEntity); Object.keys(groupEntities).forEach((grEntityId) => { const grEntity = groupEntities[grEntityId]; diff --git a/src/common/entity/has_location.js b/src/common/entity/has_location.js deleted file mode 100644 index 5cbcd3a801..0000000000 --- a/src/common/entity/has_location.js +++ /dev/null @@ -1,5 +0,0 @@ -export default function hasLocation(stateObj) { - return ( - "latitude" in stateObj.attributes && "longitude" in stateObj.attributes - ); -} diff --git a/src/common/entity/has_location.ts b/src/common/entity/has_location.ts new file mode 100644 index 0000000000..5c290887bf --- /dev/null +++ b/src/common/entity/has_location.ts @@ -0,0 +1,7 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +export default function hasLocation(stateObj: HassEntity) { + return ( + "latitude" in stateObj.attributes && "longitude" in stateObj.attributes + ); +} diff --git a/src/common/entity/input_dateteime_icon.js b/src/common/entity/input_dateteime_icon.ts similarity index 68% rename from src/common/entity/input_dateteime_icon.js rename to src/common/entity/input_dateteime_icon.ts index 8418226e90..016b19cc48 100644 --- a/src/common/entity/input_dateteime_icon.js +++ b/src/common/entity/input_dateteime_icon.ts @@ -1,7 +1,8 @@ /** Return an icon representing an input datetime state. */ import domainIcon from "./domain_icon"; +import { HassEntity } from "home-assistant-js-websocket"; -export default function inputDateTimeIcon(state) { +export default function inputDateTimeIcon(state: HassEntity): string { if (!state.attributes.has_date) { return "hass:clock"; } diff --git a/src/common/entity/sensor_icon.js b/src/common/entity/sensor_icon.ts similarity index 80% rename from src/common/entity/sensor_icon.js rename to src/common/entity/sensor_icon.ts index 44e6f3ce09..d08f7c6e97 100644 --- a/src/common/entity/sensor_icon.js +++ b/src/common/entity/sensor_icon.ts @@ -1,4 +1,5 @@ /** Return an icon representing a sensor state. */ +import { HassEntity } from "home-assistant-js-websocket"; import { UNIT_C, UNIT_F } from "../const"; import domainIcon from "./domain_icon"; @@ -9,17 +10,18 @@ const fixedDeviceClassIcons = { pressure: "hass:gauge", }; -export default function sensorIcon(state) { +export default function sensorIcon(state: HassEntity) { const dclass = state.attributes.device_class; - if (dclass in fixedDeviceClassIcons) { + if (dclass && dclass in fixedDeviceClassIcons) { return fixedDeviceClassIcons[dclass]; } if (dclass === "battery") { - if (isNaN(state.state)) { + const battery = Number(state.state); + if (isNaN(battery)) { return "hass:battery-unknown"; } - const batteryRound = Math.round(state.state / 10) * 10; + const batteryRound = Math.round(battery / 10) * 10; if (batteryRound >= 100) { return "hass:battery"; } diff --git a/src/common/entity/split_by_groups.js b/src/common/entity/split_by_groups.ts similarity index 72% rename from src/common/entity/split_by_groups.js rename to src/common/entity/split_by_groups.ts index e0631dfdda..a894472d8c 100644 --- a/src/common/entity/split_by_groups.js +++ b/src/common/entity/split_by_groups.ts @@ -1,11 +1,12 @@ import computeDomain from "./compute_domain"; +import { HassEntity, HassEntities } from "home-assistant-js-websocket"; // Split a collection into a list of groups and a 'rest' list of ungrouped // entities. // Returns { groups: [], ungrouped: {} } -export default function splitByGroups(entities) { - const groups = []; - const ungrouped = {}; +export default function splitByGroups(entities: HassEntities) { + const groups: HassEntity[] = []; + const ungrouped: HassEntities = {}; Object.keys(entities).forEach((entityId) => { const entity = entities[entityId]; diff --git a/src/common/entity/state_card_type.js b/src/common/entity/state_card_type.ts similarity index 71% rename from src/common/entity/state_card_type.js rename to src/common/entity/state_card_type.ts index 170ca9cb8b..00f6b73b6a 100644 --- a/src/common/entity/state_card_type.js +++ b/src/common/entity/state_card_type.ts @@ -1,8 +1,13 @@ +import { HassEntity } from "home-assistant-js-websocket"; import canToggleState from "./can_toggle_state"; import computeStateDomain from "./compute_state_domain"; import { DOMAINS_WITH_CARD } from "../const"; +import { HomeAssistant } from "../../types"; -export default function stateCardType(hass, stateObj) { +export default function stateCardType( + hass: HomeAssistant, + stateObj: HassEntity +) { if (stateObj.state === "unavailable") { return "display"; } diff --git a/src/common/entity/state_icon.js b/src/common/entity/state_icon.ts similarity index 87% rename from src/common/entity/state_icon.js rename to src/common/entity/state_icon.ts index 928d7bc186..351db86d12 100644 --- a/src/common/entity/state_icon.js +++ b/src/common/entity/state_icon.ts @@ -1,4 +1,5 @@ /** Return an icon representing a state. */ +import { HassEntity } from "home-assistant-js-websocket"; import { DEFAULT_DOMAIN_ICON } from "../const"; import computeDomain from "./compute_domain"; @@ -16,7 +17,7 @@ const domainIcons = { input_datetime: inputDateTimeIcon, }; -export default function stateIcon(state) { +export default function stateIcon(state: HassEntity) { if (!state) { return DEFAULT_DOMAIN_ICON; } diff --git a/src/common/entity/state_more_info_type.js b/src/common/entity/state_more_info_type.ts similarity index 73% rename from src/common/entity/state_more_info_type.js rename to src/common/entity/state_more_info_type.ts index a35ad61f35..dbdac2e198 100644 --- a/src/common/entity/state_more_info_type.js +++ b/src/common/entity/state_more_info_type.ts @@ -1,7 +1,8 @@ +import { HassEntity } from "home-assistant-js-websocket"; import computeStateDomain from "./compute_state_domain"; import { DOMAINS_HIDE_MORE_INFO, DOMAINS_WITH_MORE_INFO } from "../const"; -export default function stateMoreInfoType(stateObj) { +export default function stateMoreInfoType(stateObj: HassEntity) { const domain = computeStateDomain(stateObj); if (DOMAINS_WITH_MORE_INFO.includes(domain)) { diff --git a/src/common/entity/states_sort_by_name.js b/src/common/entity/states_sort_by_name.ts similarity index 71% rename from src/common/entity/states_sort_by_name.js rename to src/common/entity/states_sort_by_name.ts index f4b67fe2b4..e7afcd0559 100644 --- a/src/common/entity/states_sort_by_name.js +++ b/src/common/entity/states_sort_by_name.ts @@ -5,9 +5,13 @@ * const states = [state1, state2] * states.sort(statesSortByName); */ +import { HassEntity } from "home-assistant-js-websocket"; import computeStateName from "./compute_state_name"; -export default function sortStatesByName(entityA, entityB) { +export default function sortStatesByName( + entityA: HassEntity, + entityB: HassEntity +) { const nameA = computeStateName(entityA); const nameB = computeStateName(entityB); if (nameA < nameB) { diff --git a/src/common/entity/timer_time_remaining.js b/src/common/entity/timer_time_remaining.ts similarity index 55% rename from src/common/entity/timer_time_remaining.js rename to src/common/entity/timer_time_remaining.ts index 100c55a88c..05e19a46bc 100644 --- a/src/common/entity/timer_time_remaining.js +++ b/src/common/entity/timer_time_remaining.ts @@ -1,11 +1,12 @@ +import { HassEntity } from "home-assistant-js-websocket"; import durationToSeconds from "../datetime/duration_to_seconds"; -export default function timerTimeRemaining(stateObj) { +export default function timerTimeRemaining(stateObj: HassEntity) { let timeRemaining = durationToSeconds(stateObj.attributes.remaining); if (stateObj.state === "active") { - const now = new Date(); - const madeActive = new Date(stateObj.last_changed); + const now = new Date().getTime(); + const madeActive = new Date(stateObj.last_changed).getTime(); timeRemaining = Math.max(timeRemaining - (now - madeActive) / 1000, 0); } diff --git a/src/common/entity/valid_entity_id.js b/src/common/entity/valid_entity_id.js deleted file mode 100644 index 5a4ea9e23a..0000000000 --- a/src/common/entity/valid_entity_id.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function validEntityId(entityId) { - return /^(\w+)\.(\w+)$/.test(entityId); -} diff --git a/src/common/entity/valid_entity_id.ts b/src/common/entity/valid_entity_id.ts new file mode 100644 index 0000000000..29d345c633 --- /dev/null +++ b/src/common/entity/valid_entity_id.ts @@ -0,0 +1,2 @@ +const validEntityId = /^(\w+)\.(\w+)$/; +export default validEntityId.test; diff --git a/src/common/util/parse-aspect-ratio.js b/src/common/util/parse-aspect-ratio.ts similarity index 76% rename from src/common/util/parse-aspect-ratio.js rename to src/common/util/parse-aspect-ratio.ts index 1797e81e3a..cd80d4b90d 100644 --- a/src/common/util/parse-aspect-ratio.js +++ b/src/common/util/parse-aspect-ratio.ts @@ -1,9 +1,11 @@ export default function parseAspectRatio(input) { // Handle 16x9, 16:9, 1.78x1, 1.78:1, 1.78 // Ignore everything else - function parseOrThrow(number) { - const parsed = parseFloat(number); - if (isNaN(parsed)) throw new Error(`${number} is not a number`); + function parseOrThrow(num) { + const parsed = parseFloat(num); + if (isNaN(parsed)) { + throw new Error(`${num} is not a number`); + } return parsed; } try { diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index 0bda6acae7..261ad84dfa 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -211,7 +211,11 @@ export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement) > ${ this._config!.show_state !== false - ? html`