diff --git a/bower.json b/bower.json
index c1a4c8d271..bc5c123b19 100644
--- a/bower.json
+++ b/bower.json
@@ -12,7 +12,6 @@
"app-localize-behavior": "PolymerElements/app-localize-behavior#~2.0.0",
"app-route": "PolymerElements/app-route#^2.0.0",
"app-storage": "^2.0.2",
- "fecha": "~2.3.0",
"font-roboto-local": "~1.0.1",
"font-roboto": "PolymerElements/font-roboto-local#~1.0.1",
"iron-autogrow-textarea": "PolymerElements/iron-autogrow-textarea#^2.0.0",
diff --git a/js/common/config/is_component_loaded.js b/js/common/config/is_component_loaded.js
new file mode 100644
index 0000000000..c371674524
--- /dev/null
+++ b/js/common/config/is_component_loaded.js
@@ -0,0 +1,4 @@
+/** Return if a component is loaded. */
+export default function isComponentLoaded(hass, component) {
+ return hass && hass.config.core.components.indexOf(component) !== -1;
+}
diff --git a/js/common/config/location_name.js b/js/common/config/location_name.js
new file mode 100644
index 0000000000..f069a50ef8
--- /dev/null
+++ b/js/common/config/location_name.js
@@ -0,0 +1,4 @@
+/** Get the location name from a hass object. */
+export default function computeLocationName(hass) {
+ return hass && hass.config.core.location_name;
+}
diff --git a/js/common/const.js b/js/common/const.js
new file mode 100644
index 0000000000..15247103e7
--- /dev/null
+++ b/js/common/const.js
@@ -0,0 +1,42 @@
+/** Constants to be used in the frontend. */
+
+// Constants should be alphabetically sorted by name.
+// Arrays with values should be alphabetically sorted if order doesn't matter.
+// Each constant should have a description what it is supposed to be used for.
+
+/** Icon to use when no icon specified for domain. */
+export const DEFAULT_DOMAIN_ICON = 'mdi:bookmark';
+
+/** Domains that have a state card. */
+export const DOMAINS_WITH_CARD = [
+ 'climate',
+ 'cover',
+ 'configurator',
+ 'input_select',
+ 'input_number',
+ 'input_text',
+ 'media_player',
+ 'scene',
+ 'script',
+ 'timer',
+ 'weblink',
+];
+
+/** Domains that should have the history hidden in the more info dialog. */
+export const DOMAINS_MORE_INFO_NO_HISTORY = [
+ 'camera',
+ 'configurator',
+ 'history_graph',
+ 'scene',
+];
+
+/** States that we consider "off". */
+export const STATES_OFF = [
+ 'closed',
+ 'off',
+ 'unlocked',
+];
+
+/** Temperature units. */
+export const UNIT_C = '°C';
+export const UNIT_F = '°F';
diff --git a/js/common/util/duration_to_seconds.js b/js/common/datetime/duration_to_seconds.js
similarity index 100%
rename from js/common/util/duration_to_seconds.js
rename to js/common/datetime/duration_to_seconds.js
diff --git a/js/common/util/format_date.js b/js/common/datetime/format_date.js
similarity index 86%
rename from js/common/util/format_date.js
rename to js/common/datetime/format_date.js
index 006e736f88..f0e2906985 100644
--- a/js/common/util/format_date.js
+++ b/js/common/datetime/format_date.js
@@ -1,3 +1,5 @@
+import fecha from 'fecha';
+
// Check for support of native locale string options
function toLocaleDateStringSupportsOptions() {
try {
@@ -15,5 +17,5 @@ export default (toLocaleDateStringSupportsOptions() ?
{ year: 'numeric', month: 'long', day: 'numeric' },
);
} : function (dateObj, locales) { // eslint-disable-line no-unused-vars
- return window.fecha.format(dateObj, 'mediumDate');
+ return fecha.format(dateObj, 'mediumDate');
});
diff --git a/js/common/util/format_date_time.js b/js/common/datetime/format_date_time.js
similarity index 87%
rename from js/common/util/format_date_time.js
rename to js/common/datetime/format_date_time.js
index f695511b86..7fbc40c575 100644
--- a/js/common/util/format_date_time.js
+++ b/js/common/datetime/format_date_time.js
@@ -1,3 +1,5 @@
+import fecha from 'fecha';
+
// Check for support of native locale string options
function toLocaleStringSupportsOptions() {
try {
@@ -18,5 +20,5 @@ export default (toLocaleStringSupportsOptions() ?
minute: '2-digit',
});
} : function (dateObj, locales) { // eslint-disable-line no-unused-vars
- return window.fecha.format(dateObj, 'haDateTime');
+ return fecha.format(dateObj, 'haDateTime');
});
diff --git a/js/common/util/format_time.js b/js/common/datetime/format_time.js
similarity index 86%
rename from js/common/util/format_time.js
rename to js/common/datetime/format_time.js
index 5ff05a67da..cab51ed534 100644
--- a/js/common/util/format_time.js
+++ b/js/common/datetime/format_time.js
@@ -1,3 +1,5 @@
+import fecha from 'fecha';
+
// Check for support of native locale string options
function toLocaleTimeStringSupportsOptions() {
try {
@@ -15,5 +17,5 @@ export default (toLocaleTimeStringSupportsOptions() ?
{ hour: 'numeric', minute: '2-digit' }
);
} : function (dateObj, locales) { // eslint-disable-line no-unused-vars
- return window.fecha.format(dateObj, 'shortTime');
+ return fecha.format(dateObj, 'shortTime');
});
diff --git a/js/common/datetime/relative_time.js b/js/common/datetime/relative_time.js
new file mode 100644
index 0000000000..2504017a74
--- /dev/null
+++ b/js/common/datetime/relative_time.js
@@ -0,0 +1,30 @@
+/** 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) {
+ let delta = Math.abs((new Date() - dateObj) / 1000);
+ const format = delta >= 0 ? '%s ago' : 'in %s';
+
+ for (let i = 0; i < tests.length; i += 2) {
+ if (delta < tests[i]) {
+ delta = Math.floor(delta);
+ return format.replace(
+ '%s',
+ delta === 1 ? '1 ' + tests[i + 1] : delta + ' ' + tests[i + 1] + 's'
+ );
+ }
+
+ delta /= tests[i];
+ }
+
+ delta = Math.floor(delta);
+ return format.replace('%s', delta === 1 ? '1 week' : delta + ' weeks');
+}
diff --git a/js/common/util/seconds_to_duration.js b/js/common/datetime/seconds_to_duration.js
similarity index 100%
rename from js/common/util/seconds_to_duration.js
rename to js/common/datetime/seconds_to_duration.js
diff --git a/js/common/dom/apply_themes_on_element.js b/js/common/dom/apply_themes_on_element.js
new file mode 100644
index 0000000000..267b9bafd8
--- /dev/null
+++ b/js/common/dom/apply_themes_on_element.js
@@ -0,0 +1,41 @@
+/**
+ * Apply a theme to an element by setting the CSS variables on it.
+ *
+ * element: Element to apply theme on.
+ * themes: HASS Theme information
+ * localTheme: selected theme.
+ * updateMeta: boolean if we should update the theme-color meta element.
+*/
+export default function applyThemesOnElement(element, themes, localTheme, updateMeta = false) {
+ if (!element._themes) {
+ element._themes = {};
+ }
+ let themeName = themes.default_theme;
+ if (localTheme === 'default' || (localTheme && themes.themes[localTheme])) {
+ themeName = localTheme;
+ }
+ const styles = Object.assign({}, element._themes);
+ if (themeName !== 'default') {
+ var theme = themes.themes[themeName];
+ Object.keys(theme).forEach((key) => {
+ var prefixedKey = '--' + key;
+ element._themes[prefixedKey] = '';
+ styles[prefixedKey] = theme[key];
+ });
+ }
+ // implement updateStyles() method of Polemer elements
+ if (window.ShadyCSS) {
+ window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */(element), styles);
+ }
+
+ if (!updateMeta) return;
+
+ const meta = document.querySelector('meta[name=theme-color]');
+ if (meta) {
+ if (!meta.hasAttribute('default-content')) {
+ meta.setAttribute('default-content', meta.getAttribute('content'));
+ }
+ const themeColor = styles['--primary-color'] || meta.getAttribute('default-content');
+ meta.setAttribute('content', themeColor);
+ }
+}
diff --git a/js/common/dom/dynamic_content_updater.js b/js/common/dom/dynamic_content_updater.js
new file mode 100644
index 0000000000..3bdd2b0085
--- /dev/null
+++ b/js/common/dom/dynamic_content_updater.js
@@ -0,0 +1,33 @@
+/**
+ * Update root's child element to be newElementTag replacing another existing child if any.
+ * Copy attributes into the child element.
+ */
+export default function dynamicContentUpdater(root, newElementTag, attributes) {
+ const rootEl = root;
+ let customEl;
+
+ if (rootEl.lastChild && rootEl.lastChild.tagName === newElementTag) {
+ customEl = rootEl.lastChild;
+ } else {
+ if (rootEl.lastChild) {
+ rootEl.removeChild(rootEl.lastChild);
+ }
+ // Creating an element with upper case works fine in Chrome, but in FF it doesn't immediately
+ // become a defined Custom Element. Polymer does that in some later pass.
+ customEl = document.createElement(newElementTag.toLowerCase());
+ }
+
+ if (customEl.setProperties) {
+ customEl.setProperties(attributes);
+ } else {
+ // If custom element definition wasn't loaded yet - setProperties would be
+ // missing, but no harm in setting attributes one-by-one then.
+ Object.keys(attributes).forEach((key) => {
+ customEl[key] = attributes[key];
+ });
+ }
+
+ if (customEl.parentNode === null) {
+ rootEl.appendChild(customEl);
+ }
+}
diff --git a/js/common/util/attribute_class_names.js b/js/common/entity/attribute_class_names.js
similarity index 100%
rename from js/common/util/attribute_class_names.js
rename to js/common/entity/attribute_class_names.js
diff --git a/js/common/entity/binary_sensor_icon.js b/js/common/entity/binary_sensor_icon.js
new file mode 100644
index 0000000000..9aae304fc4
--- /dev/null
+++ b/js/common/entity/binary_sensor_icon.js
@@ -0,0 +1,49 @@
+/** Return an icon representing a binary sensor state. */
+
+export default function binarySensorIcon(state) {
+ var activated = state.state && state.state === 'off';
+ switch (state.attributes.device_class) {
+ case 'battery':
+ return activated ? 'mdi:battery' : 'mdi:battery-outline';
+ case 'cold':
+ return activated ? 'mdi:thermometer' : 'mdi:snowflake';
+ case 'connectivity':
+ return activated ? 'mdi:server-network-off' : 'mdi:server-network';
+ case 'door':
+ return activated ? 'mdi:door-closed' : 'mdi:door-open';
+ case 'garage_door':
+ return activated ? 'mdi:garage' : 'mdi:garage-open';
+ case 'gas':
+ case 'power':
+ case 'problem':
+ case 'safety':
+ case 'smoke':
+ return activated ? 'mdi:verified' : 'mdi:alert';
+ case 'heat':
+ return activated ? 'mdi:thermometer' : 'mdi:fire';
+ case 'light':
+ return activated ? 'mdi:brightness-5' : 'mdi:brightness-7';
+ case 'lock':
+ return activated ? 'mdi:lock' : 'mdi:lock-open';
+ case 'moisture':
+ return activated ? 'mdi:water-off' : 'mdi:water';
+ case 'motion':
+ return activated ? 'mdi:walk' : 'mdi:run';
+ case 'occupancy':
+ return activated ? 'mdi:home-outline' : 'mdi:home';
+ case 'opening':
+ return activated ? 'mdi:square' : 'mdi:square-outline';
+ case 'plug':
+ return activated ? 'mdi:power-plug-off' : 'mdi:power-plug';
+ case 'presence':
+ return activated ? 'mdi:home-outline' : 'mdi:home';
+ case 'sound':
+ return activated ? 'mdi:music-note-off' : 'mdi:music-note';
+ case 'vibration':
+ return activated ? 'mdi:crop-portrait' : 'mdi:vibrate';
+ case 'window':
+ return activated ? 'mdi:window-closed' : 'mdi:window-open';
+ default:
+ return activated ? 'mdi:radiobox-blank' : 'mdi:checkbox-marked-circle';
+ }
+}
diff --git a/js/common/util/can_toggle_domain.js b/js/common/entity/can_toggle_domain.js
similarity index 100%
rename from js/common/util/can_toggle_domain.js
rename to js/common/entity/can_toggle_domain.js
diff --git a/js/common/util/can_toggle_state.js b/js/common/entity/can_toggle_state.js
similarity index 100%
rename from js/common/util/can_toggle_state.js
rename to js/common/entity/can_toggle_state.js
diff --git a/js/common/util/compute_domain.js b/js/common/entity/compute_domain.js
similarity index 100%
rename from js/common/util/compute_domain.js
rename to js/common/entity/compute_domain.js
diff --git a/js/common/entity/compute_object_id.js b/js/common/entity/compute_object_id.js
new file mode 100644
index 0000000000..bcb3f9627c
--- /dev/null
+++ b/js/common/entity/compute_object_id.js
@@ -0,0 +1,4 @@
+/** Compute the object ID of a state. */
+export default function computeObjectId(entityId) {
+ return entityId.substr(entityId.indexOf('.') + 1);
+}
diff --git a/js/common/util/compute_state_display.js b/js/common/entity/compute_state_display.js
similarity index 94%
rename from js/common/util/compute_state_display.js
rename to js/common/entity/compute_state_display.js
index f62e1a0318..24e56393dd 100644
--- a/js/common/util/compute_state_display.js
+++ b/js/common/entity/compute_state_display.js
@@ -1,7 +1,7 @@
import computeStateDomain from './compute_state_domain.js';
-import formatDateTime from './format_date_time.js';
-import formatDate from './format_date.js';
-import formatTime from './format_time.js';
+import formatDateTime from '../datetime/format_date_time.js';
+import formatDate from '../datetime/format_date.js';
+import formatTime from '../datetime/format_time.js';
export default function computeStateDisplay(localize, stateObj, language) {
if (!stateObj._stateDisplay) {
diff --git a/js/common/util/compute_state_domain.js b/js/common/entity/compute_state_domain.js
similarity index 100%
rename from js/common/util/compute_state_domain.js
rename to js/common/entity/compute_state_domain.js
diff --git a/js/common/entity/compute_state_name.js b/js/common/entity/compute_state_name.js
new file mode 100644
index 0000000000..c9297ef226
--- /dev/null
+++ b/js/common/entity/compute_state_name.js
@@ -0,0 +1,11 @@
+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/js/common/entity/cover_icon.js b/js/common/entity/cover_icon.js
new file mode 100644
index 0000000000..b3377a94ba
--- /dev/null
+++ b/js/common/entity/cover_icon.js
@@ -0,0 +1,12 @@
+/** Return an icon representing a cover state. */
+import domainIcon from './domain_icon.js';
+
+export default function coverIcon(state) {
+ var open = state.state && state.state !== 'closed';
+ switch (state.attributes.device_class) {
+ case 'garage':
+ return open ? 'mdi:garage-open' : 'mdi:garage';
+ default:
+ return domainIcon('cover', state.state);
+ }
+}
diff --git a/js/common/entity/domain_icon.js b/js/common/entity/domain_icon.js
new file mode 100644
index 0000000000..fb53b45cba
--- /dev/null
+++ b/js/common/entity/domain_icon.js
@@ -0,0 +1,95 @@
+/**
+ * Return the icon to be used for a domain.
+ *
+ * Optionally pass in a state to influence the domain icon.
+ */
+import { DEFAULT_DOMAIN_ICON } from '../const.js';
+
+const fixedIcons = {
+ automation: 'mdi:playlist-play',
+ calendar: 'mdi:calendar',
+ camera: 'mdi:video',
+ climate: 'mdi:thermostat',
+ configurator: 'mdi:settings',
+ conversation: 'mdi:text-to-speech',
+ device_tracker: 'mdi:account',
+ fan: 'mdi:fan',
+ group: 'mdi:google-circles-communities',
+ history_graph: 'mdi:chart-line',
+ homeassistant: 'mdi:home-assistant',
+ image_processing: 'mdi:image-filter-frames',
+ input_boolean: 'mdi:drawing',
+ input_datetime: 'mdi:calendar-clock',
+ input_number: 'mdi:ray-vertex',
+ input_select: 'mdi:format-list-bulleted',
+ input_text: 'mdi:textbox',
+ light: 'mdi:lightbulb',
+ mailbox: 'mdi:mailbox',
+ notify: 'mdi:comment-alert',
+ plant: 'mdi:flower',
+ proximity: 'mdi:apple-safari',
+ remote: 'mdi:remote',
+ scene: 'mdi:google-pages',
+ script: 'mdi:file-document',
+ sensor: 'mdi:eye',
+ simple_alarm: 'mdi:bell',
+ sun: 'mdi:white-balance-sunny',
+ switch: 'mdi:flash',
+ timer: 'mdi:timer',
+ updater: 'mdi:cloud-upload',
+ vacuum: 'mdi:robot-vacuum',
+ weblink: 'mdi:open-in-new',
+};
+
+export default function domainIcon(domain, state) {
+ if (domain in fixedIcons) {
+ return fixedIcons[domain];
+ }
+
+ switch (domain) {
+ case 'alarm_control_panel':
+ switch (state) {
+ case 'armed_home':
+ return 'mdi:bell-plus';
+ case 'armed_night':
+ return 'mdi:bell-sleep';
+ case 'disarmed':
+ return 'mdi:bell-outline';
+ case 'triggered':
+ return 'mdi:bell-ring';
+ default:
+ return 'mdi:bell';
+ }
+
+ case 'binary_sensor':
+ return state && state === 'off' ? 'mdi:radiobox-blank' : 'mdi:checkbox-marked-circle';
+
+ case 'cover':
+ return state && state === 'open' ? 'mdi:window-open' : 'mdi:window-closed';
+
+ case 'lock':
+ return state && state === 'unlocked' ? 'mdi:lock-open' : 'mdi:lock';
+
+ case 'media_player':
+ return state && state !== 'off' && state !== 'idle' ?
+ 'mdi:cast-connected' : 'mdi:cast';
+
+ case 'zwave':
+ switch (state) {
+ case 'dead':
+ return 'mdi:emoticon-dead';
+ case 'sleeping':
+ return 'mdi:sleep';
+ case 'initializing':
+ return 'mdi:timer-sand';
+ default:
+ return 'mdi:nfc';
+ }
+
+ default:
+ /* eslint-disable no-console */
+ console.warn('Unable to find icon for domain ' + domain + ' (' + state + ')');
+ /* eslint-enable no-console */
+ return DEFAULT_DOMAIN_ICON;
+ }
+}
diff --git a/js/common/util/feature_class_names.js b/js/common/entity/feature_class_names.js
similarity index 100%
rename from js/common/util/feature_class_names.js
rename to js/common/entity/feature_class_names.js
diff --git a/js/common/util/location.js b/js/common/entity/has_location.js
similarity index 66%
rename from js/common/util/location.js
rename to js/common/entity/has_location.js
index de86471ee0..6ec987d7ae 100644
--- a/js/common/util/location.js
+++ b/js/common/entity/has_location.js
@@ -1,4 +1,4 @@
-export function hasLocation(stateObj) {
+export default function hasLocation(stateObj) {
return ('latitude' in stateObj.attributes &&
'longitude' in stateObj.attributes);
}
diff --git a/js/common/entity/input_dateteime_icon.js b/js/common/entity/input_dateteime_icon.js
new file mode 100644
index 0000000000..bc892bb264
--- /dev/null
+++ b/js/common/entity/input_dateteime_icon.js
@@ -0,0 +1,11 @@
+/** Return an icon representing an input datetime state. */
+import domainIcon from './domain_icon.js';
+
+export default function inputDateTimeIcon(state) {
+ if (!state.attributes.has_date) {
+ return 'mdi:clock';
+ } else if (!state.attributes.has_time) {
+ return 'mdi:calendar';
+ }
+ return domainIcon('input_datetime');
+}
diff --git a/js/common/entity/sensor_icon.js b/js/common/entity/sensor_icon.js
new file mode 100644
index 0000000000..88d8992ab4
--- /dev/null
+++ b/js/common/entity/sensor_icon.js
@@ -0,0 +1,35 @@
+/** Return an icon representing a sensor state. */
+import { UNIT_C, UNIT_F } from '../const.js';
+import domainIcon from './domain_icon.js';
+
+const fixedDeviceClassIcons = {
+ humidity: 'mdi:water-percent',
+ illuminance: 'mdi:brightness-5',
+ temperature: 'mdi:thermometer',
+};
+
+export default function sensorIcon(state) {
+ const dclass = state.attributes.device_class;
+
+ if (dclass in fixedDeviceClassIcons) {
+ return fixedDeviceClassIcons[dclass];
+ } else if (dclass === 'battery') {
+ if (isNaN(state.state)) {
+ return 'mdi:battery-unknown';
+ }
+ const batteryRound = Math.round(state.state / 10) * 10;
+ if (batteryRound >= 100) {
+ return 'mdi:battery';
+ }
+ if (batteryRound <= 0) {
+ return 'mdi:battery-alert';
+ }
+ return `mdi:battery-${batteryRound}`;
+ }
+
+ const unit = state.attributes.unit_of_measurement;
+ if (unit === UNIT_C || unit === UNIT_F) {
+ return 'mdi:thermometer';
+ }
+ return domainIcon('sensor');
+}
diff --git a/js/common/util/state_card_type.js b/js/common/entity/state_card_type.js
similarity index 72%
rename from js/common/util/state_card_type.js
rename to js/common/entity/state_card_type.js
index 65526a1991..b85153cdac 100644
--- a/js/common/util/state_card_type.js
+++ b/js/common/entity/state_card_type.js
@@ -1,19 +1,6 @@
import canToggleState from './can_toggle_state.js';
import computeStateDomain from './compute_state_domain.js';
-
-const DOMAINS_WITH_CARD = [
- 'climate',
- 'cover',
- 'configurator',
- 'input_select',
- 'input_number',
- 'input_text',
- 'media_player',
- 'scene',
- 'script',
- 'timer',
- 'weblink',
-];
+import { DOMAINS_WITH_CARD } from '../const.js';
export default function stateCardType(hass, stateObj) {
if (stateObj.state === 'unavailable') {
diff --git a/js/common/entity/state_icon.js b/js/common/entity/state_icon.js
new file mode 100644
index 0000000000..b6fabf5a48
--- /dev/null
+++ b/js/common/entity/state_icon.js
@@ -0,0 +1,32 @@
+/** Return an icon representing a state. */
+import { DEFAULT_DOMAIN_ICON } from '../const.js';
+
+import computeDomain from './compute_domain.js';
+import domainIcon from './domain_icon.js';
+
+import binarySensorIcon from './binary_sensor_icon.js';
+import coverIcon from './cover_icon.js';
+import sensorIcon from './sensor_icon.js';
+import inputDateTimeIcon from './input_dateteime_icon.js';
+
+const domainIcons = {
+ binary_sensor: binarySensorIcon,
+ cover: coverIcon,
+ sensor: sensorIcon,
+ input_datetime: inputDateTimeIcon,
+};
+
+export default function stateIcon(state) {
+ if (!state) {
+ return DEFAULT_DOMAIN_ICON;
+ } else if (state.attributes.icon) {
+ return state.attributes.icon;
+ }
+
+ const domain = computeDomain(state.entity_id);
+
+ if (domain in domainIcons) {
+ return domainIcons[domain](state);
+ }
+ return domainIcon(domain, state.state);
+}
diff --git a/js/common/util/state_more_info_type.js b/js/common/entity/state_more_info_type.js
similarity index 100%
rename from js/common/util/state_more_info_type.js
rename to js/common/entity/state_more_info_type.js
diff --git a/js/common/entity/states_sort_by_name.js b/js/common/entity/states_sort_by_name.js
new file mode 100644
index 0000000000..3dbd2f8cce
--- /dev/null
+++ b/js/common/entity/states_sort_by_name.js
@@ -0,0 +1,20 @@
+/**
+ * Sort function to help sort states by name
+ *
+ * Usage:
+ * const states = [state1, state2]
+ * states.sort(statesSortByName);
+ */
+import computeStateName from './compute_state_name.js';
+
+export default function sortStatesByName(entityA, entityB) {
+ const nameA = computeStateName(entityA);
+ const nameB = computeStateName(entityB);
+ if (nameA < nameB) {
+ return -1;
+ }
+ if (nameA > nameB) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/js/common/util/timer_time_remaining.js b/js/common/entity/timer_time_remaining.js
similarity index 84%
rename from js/common/util/timer_time_remaining.js
rename to js/common/entity/timer_time_remaining.js
index 2d527131ea..e401264bff 100644
--- a/js/common/util/timer_time_remaining.js
+++ b/js/common/entity/timer_time_remaining.js
@@ -1,4 +1,4 @@
-import durationToSeconds from './duration_to_seconds.js';
+import durationToSeconds from '../datetime/duration_to_seconds.js';
export default function timerTimeRemaining(stateObj) {
let timeRemaining = durationToSeconds(stateObj.attributes.remaining);
diff --git a/js/common/entity/valid_entity_id.js b/js/common/entity/valid_entity_id.js
new file mode 100644
index 0000000000..5a4ea9e23a
--- /dev/null
+++ b/js/common/entity/valid_entity_id.js
@@ -0,0 +1,3 @@
+export default function validEntityId(entityId) {
+ return /^(\w+)\.(\w+)$/.test(entityId);
+}
diff --git a/js/common/util/event.js b/js/common/preact/event.js
similarity index 100%
rename from js/common/util/event.js
rename to js/common/preact/event.js
diff --git a/js/common/util/entity.js b/js/common/util/entity.js
deleted file mode 100644
index 50670c8a33..0000000000
--- a/js/common/util/entity.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export function validEntityId(entityId) {
- return /^(\w+)\.(\w+)$/.test(entityId);
-}
diff --git a/js/panel-config/condition/numeric_state.js b/js/panel-config/condition/numeric_state.js
index 2e52cf11d7..b7adfad3b3 100644
--- a/js/panel-config/condition/numeric_state.js
+++ b/js/panel-config/condition/numeric_state.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class NumericStateCondition extends Component {
constructor() {
diff --git a/js/panel-config/condition/state.js b/js/panel-config/condition/state.js
index 362b6a8893..e5580c63a4 100644
--- a/js/panel-config/condition/state.js
+++ b/js/panel-config/condition/state.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class StateCondition extends Component {
constructor() {
diff --git a/js/panel-config/condition/sun.js b/js/panel-config/condition/sun.js
index 087e7a00c3..2fe35c9ecd 100644
--- a/js/panel-config/condition/sun.js
+++ b/js/panel-config/condition/sun.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class SunCondition extends Component {
constructor() {
diff --git a/js/panel-config/condition/template.js b/js/panel-config/condition/template.js
index 728274414d..2f6481e55c 100644
--- a/js/panel-config/condition/template.js
+++ b/js/panel-config/condition/template.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class TemplateCondition extends Component {
constructor() {
diff --git a/js/panel-config/condition/time.js b/js/panel-config/condition/time.js
index 57f1c614df..ded00c3c75 100644
--- a/js/panel-config/condition/time.js
+++ b/js/panel-config/condition/time.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class TimeCondition extends Component {
constructor() {
diff --git a/js/panel-config/condition/zone.js b/js/panel-config/condition/zone.js
index ca6bc8ac23..d82c69a6c3 100644
--- a/js/panel-config/condition/zone.js
+++ b/js/panel-config/condition/zone.js
@@ -1,8 +1,8 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
-import { hasLocation } from '../../common/util/location.js';
-import computeStateDomain from '../../common/util/compute_state_domain.js';
+import { onChangeEvent } from '../../common/preact/event.js';
+import hasLocation from '../../common/entity/has_location.js';
+import computeStateDomain from '../../common/entity/compute_state_domain.js';
function zoneAndLocationFilter(stateObj) {
return hasLocation(stateObj) && computeStateDomain(stateObj) !== 'zone';
diff --git a/js/panel-config/script/delay.js b/js/panel-config/script/delay.js
index 02add9fe3f..a962f330d0 100644
--- a/js/panel-config/script/delay.js
+++ b/js/panel-config/script/delay.js
@@ -1,5 +1,5 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class DelayAction extends Component {
constructor() {
diff --git a/js/panel-config/script/event.js b/js/panel-config/script/event.js
index 1013ecf9db..643d1b57f7 100644
--- a/js/panel-config/script/event.js
+++ b/js/panel-config/script/event.js
@@ -1,7 +1,7 @@
import { h, Component } from 'preact';
import JSONTextArea from '../json_textarea.js';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class EventAction extends Component {
constructor() {
diff --git a/js/panel-config/script/wait.js b/js/panel-config/script/wait.js
index 22ac63ba28..0032cc17f8 100644
--- a/js/panel-config/script/wait.js
+++ b/js/panel-config/script/wait.js
@@ -1,5 +1,5 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class WaitAction extends Component {
constructor() {
diff --git a/js/panel-config/trigger/event.js b/js/panel-config/trigger/event.js
index 7c8b0a8219..68a6c324ed 100644
--- a/js/panel-config/trigger/event.js
+++ b/js/panel-config/trigger/event.js
@@ -1,7 +1,7 @@
import { h, Component } from 'preact';
import JSONTextArea from '../json_textarea.js';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class EventTrigger extends Component {
constructor() {
diff --git a/js/panel-config/trigger/mqtt.js b/js/panel-config/trigger/mqtt.js
index fb66a8c803..5f4c2bd012 100644
--- a/js/panel-config/trigger/mqtt.js
+++ b/js/panel-config/trigger/mqtt.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class MQTTTrigger extends Component {
constructor() {
diff --git a/js/panel-config/trigger/numeric_state.js b/js/panel-config/trigger/numeric_state.js
index ab20d86fdd..3aa85978cf 100644
--- a/js/panel-config/trigger/numeric_state.js
+++ b/js/panel-config/trigger/numeric_state.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class NumericStateTrigger extends Component {
constructor() {
diff --git a/js/panel-config/trigger/state.js b/js/panel-config/trigger/state.js
index dd16e626eb..461a4a5789 100644
--- a/js/panel-config/trigger/state.js
+++ b/js/panel-config/trigger/state.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class StateTrigger extends Component {
constructor() {
diff --git a/js/panel-config/trigger/sun.js b/js/panel-config/trigger/sun.js
index 098940b2de..8140095e7a 100644
--- a/js/panel-config/trigger/sun.js
+++ b/js/panel-config/trigger/sun.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class SunTrigger extends Component {
constructor() {
diff --git a/js/panel-config/trigger/template.js b/js/panel-config/trigger/template.js
index 72f99e1613..527eb3c208 100644
--- a/js/panel-config/trigger/template.js
+++ b/js/panel-config/trigger/template.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class TemplateTrigger extends Component {
constructor() {
diff --git a/js/panel-config/trigger/time.js b/js/panel-config/trigger/time.js
index 929adec240..58c6e67ac4 100644
--- a/js/panel-config/trigger/time.js
+++ b/js/panel-config/trigger/time.js
@@ -1,6 +1,6 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
+import { onChangeEvent } from '../../common/preact/event.js';
export default class TimeTrigger extends Component {
constructor() {
diff --git a/js/panel-config/trigger/zone.js b/js/panel-config/trigger/zone.js
index 47996cffa3..b918cdd0c1 100644
--- a/js/panel-config/trigger/zone.js
+++ b/js/panel-config/trigger/zone.js
@@ -1,8 +1,8 @@
import { h, Component } from 'preact';
-import { onChangeEvent } from '../../common/util/event.js';
-import { hasLocation } from '../../common/util/location.js';
-import computeStateDomain from '../../common/util/compute_state_domain.js';
+import { onChangeEvent } from '../../common/preact/event.js';
+import hasLocation from '../../common/entity/has_location.js';
+import computeStateDomain from '../../common/entity/compute_state_domain.js';
function zoneAndLocationFilter(stateObj) {
return hasLocation(stateObj) && computeStateDomain(stateObj) !== 'zone';
diff --git a/js/util.js b/js/util.js
index b970514fd4..70996c0f55 100644
--- a/js/util.js
+++ b/js/util.js
@@ -5,42 +5,93 @@
* ES6 JS imports. Once we move to Polymer 3, we should be able to simply
* import these functions where we need them.
*/
+import fecha from 'fecha';
-import attributeClassNames from './common/util/attribute_class_names.js';
-import canToggleDomain from './common/util/can_toggle_domain.js';
-import canToggleState from './common/util/can_toggle_state.js';
-import computeStateDisplay from './common/util/compute_state_display.js';
-import computeDomain from './common/util/compute_state_domain.js';
-import durationToSeconds from './common/util/duration_to_seconds.js';
-import featureClassNames from './common/util/feature_class_names.js';
-import formatDate from './common/util/format_date.js';
-import formatDateTime from './common/util/format_date_time.js';
-import formatTime from './common/util/format_time.js';
-import secondsToDuration from './common/util/seconds_to_duration.js';
-import stateCardType from './common/util/state_card_type.js';
-import stateMoreInfoType from './common/util/state_more_info_type.js';
-import timerTimeRemaining from './common/util/timer_time_remaining.js';
+// const
+import {
+ DEFAULT_DOMAIN_ICON,
+ DOMAINS_MORE_INFO_NO_HISTORY,
+ STATES_OFF,
+} from './common/const.js';
-window.hassUtil = window.hassUtil || {};
+// config
+import computeLocationName from './common/config/location_name';
+import isComponentLoaded from './common/config/is_component_loaded.js';
+
+// dom
+import applyThemesOnElement from './common/dom/apply_themes_on_element.js';
+import dynamicContentUpdater from './common/dom/dynamic_content_updater.js';
+
+// datetime
+import durationToSeconds from './common/datetime/duration_to_seconds.js';
+import formatDate from './common/datetime/format_date.js';
+import formatDateTime from './common/datetime/format_date_time.js';
+import formatTime from './common/datetime/format_time.js';
+import relativeTime from './common/datetime/relative_time.js';
+import secondsToDuration from './common/datetime/seconds_to_duration.js';
+
+// entity
+import attributeClassNames from './common/entity/attribute_class_names.js';
+import binarySensorIcon from './common/entity/binary_sensor_icon.js';
+import canToggleDomain from './common/entity/can_toggle_domain.js';
+import canToggleState from './common/entity/can_toggle_state.js';
+import computeDomain from './common/entity/compute_state_domain.js';
+import computeObjectId from './common/entity/compute_object_id.js';
+import computeStateDisplay from './common/entity/compute_state_display.js';
+import computeStateName from './common/entity/compute_state_name.js';
+import coverIcon from './common/entity/cover_icon.js';
+import domainIcon from './common/entity/domain_icon.js';
+import featureClassNames from './common/entity/feature_class_names.js';
+import sensorIcon from './common/entity/sensor_icon.js';
+import sortByName from './common/entity/states_sort_by_name.js';
+import stateCardType from './common/entity/state_card_type.js';
+import stateIcon from './common/entity/state_icon.js';
+import stateMoreInfoType from './common/entity/state_more_info_type.js';
+import timerTimeRemaining from './common/entity/timer_time_remaining.js';
const language = navigator.languages ?
navigator.languages[0] : navigator.language || navigator.userLanguage;
-window.fecha.masks.haDateTime = window.fecha.masks.shortTime + ' ' + window.fecha.masks.mediumDate;
+fecha.masks.haDateTime = `${fecha.masks.shortTime} ${fecha.masks.mediumDate}`;
-Object.assign(window.hassUtil, {
- attributeClassNames,
- canToggleDomain,
- canToggleState,
- computeDomain,
- computeStateDisplay,
+window.hassUtil = {
+ // const
+ DEFAULT_ICON: DEFAULT_DOMAIN_ICON,
+ OFF_STATES: STATES_OFF,
+ DOMAINS_WITH_NO_HISTORY: DOMAINS_MORE_INFO_NO_HISTORY,
+
+ // config
+ computeLocationName,
+ isComponentLoaded,
+
+ // datetime
durationToSeconds,
- featureClassNames,
- secondsToDuration,
- stateCardType,
- stateMoreInfoType,
- timerTimeRemaining,
formatDate: dateObj => formatDate(dateObj, language),
formatDateTime: dateObj => formatDateTime(dateObj, language),
formatTime: dateObj => formatTime(dateObj, language),
-});
+ relativeTime,
+
+ // dom
+ applyThemesOnElement,
+ dynamicContentUpdater,
+
+ // entity
+ attributeClassNames,
+ binarySensorIcon,
+ canToggleDomain,
+ canToggleState,
+ computeDomain,
+ computeObjectId,
+ computeStateDisplay,
+ computeStateName,
+ coverIcon,
+ domainIcon,
+ featureClassNames,
+ secondsToDuration,
+ sensorIcon,
+ sortByName,
+ stateCardType,
+ stateIcon,
+ stateMoreInfoType,
+ timerTimeRemaining,
+};
diff --git a/package.json b/package.json
index 7f2f964a0d..846e55e2e7 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"license": "Apache-2.0",
"dependencies": {
"es6-object-assign": "^1.1.0",
+ "fecha": "^2.3.3",
"home-assistant-js-websocket": "^1.1.2",
"mdn-polyfills": "^5.5.0",
"preact": "^8.2.6",
diff --git a/panels/config/script/ha-script-editor.html b/panels/config/script/ha-script-editor.html
index 59c7560775..6288508358 100644
--- a/panels/config/script/ha-script-editor.html
+++ b/panels/config/script/ha-script-editor.html
@@ -209,7 +209,7 @@ class HaScriptEditor extends window.hassMixins.LocalizeMixin(Polymer.Element) {
if (oldVal && oldVal.entity_id === newVal.entity_id) {
return;
}
- this.hass.callApi('get', 'config/script/config/' + window.hassUtil.computeObjectId(newVal))
+ this.hass.callApi('get', 'config/script/config/' + window.hassUtil.computeObjectId(newVal.entity_id))
.then((config) => {
// Normalize data: ensure sequence is a list
// Happens when people copy paste their scripts into the config
@@ -266,7 +266,8 @@ class HaScriptEditor extends window.hassMixins.LocalizeMixin(Polymer.Element) {
}
saveScript() {
- var id = this.creatingNew ? '' + Date.now() : window.hassUtil.computeObjectId(this.script);
+ var id = this.creatingNew ?
+ '' + Date.now() : window.hassUtil.computeObjectId(this.script.entity_id);
this.hass.callApi('post', 'config/script/config/' + id, this.config).then(() => {
this.dirty = false;
diff --git a/src/util/hass-util.html b/src/util/hass-util.html
index 522d1dcb0a..55aa092345 100644
--- a/src/util/hass-util.html
+++ b/src/util/hass-util.html
@@ -1,415 +1 @@
-
-
-
-
diff --git a/test-mocha/common/util/duration_to_seconds_test.js b/test-mocha/common/datetime/duration_to_seconds_test.js
similarity index 72%
rename from test-mocha/common/util/duration_to_seconds_test.js
rename to test-mocha/common/datetime/duration_to_seconds_test.js
index 1454574e70..c4e503cee9 100644
--- a/test-mocha/common/util/duration_to_seconds_test.js
+++ b/test-mocha/common/datetime/duration_to_seconds_test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import durationToSeconds from '../../../js/common/util/duration_to_seconds.js';
+import durationToSeconds from '../../../js/common/datetime/duration_to_seconds.js';
describe('durationToSeconds', () => {
it('works', () => {
diff --git a/test-mocha/common/util/format_date.js b/test-mocha/common/datetime/format_date.js
similarity index 88%
rename from test-mocha/common/util/format_date.js
rename to test-mocha/common/datetime/format_date.js
index 817fd9bb5a..28fe0c2680 100644
--- a/test-mocha/common/util/format_date.js
+++ b/test-mocha/common/datetime/format_date.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import formatDate from '../../../js/common/util/format_date';
+import formatDate from '../../../js/common/datetime/format_date';
describe('formatDate', () => {
const dateObj = new Date(
diff --git a/test-mocha/common/util/format_date_time.js b/test-mocha/common/datetime/format_date_time.js
similarity index 87%
rename from test-mocha/common/util/format_date_time.js
rename to test-mocha/common/datetime/format_date_time.js
index 94540a0236..0c800d631d 100644
--- a/test-mocha/common/util/format_date_time.js
+++ b/test-mocha/common/datetime/format_date_time.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import formatDateTime from '../../../js/common/util/format_date_time';
+import formatDateTime from '../../../js/common/datetime/format_date_time';
describe('formatDateTime', () => {
const dateObj = new Date(
diff --git a/test-mocha/common/util/format_time.js b/test-mocha/common/datetime/format_time.js
similarity index 87%
rename from test-mocha/common/util/format_time.js
rename to test-mocha/common/datetime/format_time.js
index 1e36c3e5d9..92f1a5513a 100644
--- a/test-mocha/common/util/format_time.js
+++ b/test-mocha/common/datetime/format_time.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import formatTime from '../../../js/common/util/format_time';
+import formatTime from '../../../js/common/datetime/format_time';
describe('formatTime', () => {
const dateObj = new Date(
diff --git a/test-mocha/common/util/seconds_to_duration_test.js b/test-mocha/common/datetime/seconds_to_duration_test.js
similarity index 79%
rename from test-mocha/common/util/seconds_to_duration_test.js
rename to test-mocha/common/datetime/seconds_to_duration_test.js
index ba7088c467..35a9b1e457 100644
--- a/test-mocha/common/util/seconds_to_duration_test.js
+++ b/test-mocha/common/datetime/seconds_to_duration_test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import secondsToDuration from '../../../js/common/util/seconds_to_duration.js';
+import secondsToDuration from '../../../js/common/datetime/seconds_to_duration.js';
describe('secondsToDuration', () => {
it('works', () => {
diff --git a/test-mocha/common/util/attribute_class_names_test.js b/test-mocha/common/entity/attribute_class_names_test.js
similarity index 92%
rename from test-mocha/common/util/attribute_class_names_test.js
rename to test-mocha/common/entity/attribute_class_names_test.js
index 309dd83b35..278880e56c 100644
--- a/test-mocha/common/util/attribute_class_names_test.js
+++ b/test-mocha/common/entity/attribute_class_names_test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import attributeClassNames from '../../../js/common/util/attribute_class_names';
+import attributeClassNames from '../../../js/common/entity/attribute_class_names';
describe('attributeClassNames', () => {
const attrs = ['mock_attr1', 'mock_attr2'];
diff --git a/test-mocha/common/util/can_toggle_domain_test.js b/test-mocha/common/entity/can_toggle_domain_test.js
similarity index 91%
rename from test-mocha/common/util/can_toggle_domain_test.js
rename to test-mocha/common/entity/can_toggle_domain_test.js
index a357cd5c0e..0a422900e4 100644
--- a/test-mocha/common/util/can_toggle_domain_test.js
+++ b/test-mocha/common/entity/can_toggle_domain_test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import canToggleDomain from '../../../js/common/util/can_toggle_domain';
+import canToggleDomain from '../../../js/common/entity/can_toggle_domain';
describe('canToggleDomain', () => {
const hass = {
diff --git a/test-mocha/common/util/can_toggle_state_test.js b/test-mocha/common/entity/can_toggle_state_test.js
similarity index 94%
rename from test-mocha/common/util/can_toggle_state_test.js
rename to test-mocha/common/entity/can_toggle_state_test.js
index d5159864da..9055ceb087 100644
--- a/test-mocha/common/util/can_toggle_state_test.js
+++ b/test-mocha/common/entity/can_toggle_state_test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import canToggleState from '../../../js/common/util/can_toggle_state';
+import canToggleState from '../../../js/common/entity/can_toggle_state';
describe('canToggleState', () => {
const hass = {
diff --git a/test-mocha/common/util/compute_domain.js b/test-mocha/common/entity/compute_domain.js
similarity index 84%
rename from test-mocha/common/util/compute_domain.js
rename to test-mocha/common/entity/compute_domain.js
index 708f5ff215..c949c3e43e 100644
--- a/test-mocha/common/util/compute_domain.js
+++ b/test-mocha/common/entity/compute_domain.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import computeDomain from '../../../js/common/util/compute_domain';
+import computeDomain from '../../../js/common/entity/compute_domain';
describe('computeDomain', () => {
it('Returns domains', () => {
diff --git a/test-mocha/common/util/compute_state_display.js b/test-mocha/common/entity/compute_state_display.js
similarity index 98%
rename from test-mocha/common/util/compute_state_display.js
rename to test-mocha/common/entity/compute_state_display.js
index ce925dc43e..b871a5923f 100644
--- a/test-mocha/common/util/compute_state_display.js
+++ b/test-mocha/common/entity/compute_state_display.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import computeStateDisplay from '../../../js/common/util/compute_state_display';
+import computeStateDisplay from '../../../js/common/entity/compute_state_display';
describe('computeStateDisplay', () => {
const localize = function (message, ...args) {
diff --git a/test-mocha/common/util/compute_state_domain.js b/test-mocha/common/entity/compute_state_domain.js
similarity index 74%
rename from test-mocha/common/util/compute_state_domain.js
rename to test-mocha/common/entity/compute_state_domain.js
index 38620bd60b..662e6e1516 100644
--- a/test-mocha/common/util/compute_state_domain.js
+++ b/test-mocha/common/entity/compute_state_domain.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import computeStateDomain from '../../../js/common/util/compute_state_domain.js';
+import computeStateDomain from '../../../js/common/entity/compute_state_domain.js';
describe('computeStateDomain', () => {
it('Detects sensor domain', () => {
diff --git a/test-mocha/common/util/feature_class_names_test.js b/test-mocha/common/entity/feature_class_names_test.js
similarity index 93%
rename from test-mocha/common/util/feature_class_names_test.js
rename to test-mocha/common/entity/feature_class_names_test.js
index 11ea64b7c8..623ff1d735 100644
--- a/test-mocha/common/util/feature_class_names_test.js
+++ b/test-mocha/common/entity/feature_class_names_test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import featureClassNames from '../../../js/common/util/feature_class_names';
+import featureClassNames from '../../../js/common/entity/feature_class_names';
describe('featureClassNames', () => {
const classNames = {
diff --git a/test-mocha/common/util/location.js b/test-mocha/common/entity/has_location.test.js
similarity index 91%
rename from test-mocha/common/util/location.js
rename to test-mocha/common/entity/has_location.test.js
index fefc90e6af..b5ed28b651 100644
--- a/test-mocha/common/util/location.js
+++ b/test-mocha/common/entity/has_location.test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import { hasLocation } from '../../../js/common/util/location';
+import hasLocation from '../../../js/common/entity/has_location.js';
describe('hasLocation', () => {
it('flags states with location', () => {
diff --git a/test-mocha/common/util/state_card_type_test.js b/test-mocha/common/entity/state_card_type_test.js
similarity index 94%
rename from test-mocha/common/util/state_card_type_test.js
rename to test-mocha/common/entity/state_card_type_test.js
index 76fe75a526..11d149d4b3 100644
--- a/test-mocha/common/util/state_card_type_test.js
+++ b/test-mocha/common/entity/state_card_type_test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import stateCardType from '../../../js/common/util/state_card_type';
+import stateCardType from '../../../js/common/entity/state_card_type.js';
describe('stateCardType', () => {
const hass = {
diff --git a/test-mocha/common/util/state_more_info_type_test.js b/test-mocha/common/entity/state_more_info_type_test.js
similarity index 89%
rename from test-mocha/common/util/state_more_info_type_test.js
rename to test-mocha/common/entity/state_more_info_type_test.js
index b9e5fa4fed..7d3afe770b 100644
--- a/test-mocha/common/util/state_more_info_type_test.js
+++ b/test-mocha/common/entity/state_more_info_type_test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
-import stateMoreInfoType from '../../../js/common/util/state_more_info_type';
+import stateMoreInfoType from '../../../js/common/entity/state_more_info_type.js';
describe('stateMoreInfoType', () => {
it('Returns media_player for media_player states', () => {
diff --git a/test-mocha/common/util/timer_time_remaining_test.js b/test-mocha/common/entity/timer_time_remaining_test.js
similarity index 91%
rename from test-mocha/common/util/timer_time_remaining_test.js
rename to test-mocha/common/entity/timer_time_remaining_test.js
index 8ab2678c0e..f2e69ec7a5 100644
--- a/test-mocha/common/util/timer_time_remaining_test.js
+++ b/test-mocha/common/entity/timer_time_remaining_test.js
@@ -1,7 +1,7 @@
import { assert } from 'chai';
import sinon from 'sinon';
-import timerTimeRemaining from '../../../js/common/util/timer_time_remaining.js';
+import timerTimeRemaining from '../../../js/common/entity/timer_time_remaining.js';
describe('timerTimeRemaining', () => {
it('works with idle timers', () => {
diff --git a/yarn.lock b/yarn.lock
index e43d83ffa2..e223128240 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3277,6 +3277,10 @@ feature-detect-es6@^1.3.1:
dependencies:
array-back "^1.0.3"
+fecha@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd"
+
figures@^1.3.5:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"