Add demo (#2502)
* Add demo * Fix stuff * Lint * Typescript and demo card * More fixes * Allow switching through configs * Lint * Lint2 * Add two demo configs * Lint * Lint
@ -18,6 +18,7 @@
|
||||
},
|
||||
"globals": {
|
||||
"__DEV__": false,
|
||||
"__DEMO__": false,
|
||||
"__BUILD__": false,
|
||||
"__VERSION__": false,
|
||||
"__STATIC_PATH__": false,
|
||||
|
BIN
demo/public/assets/jimpower/background-15.jpg
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
demo/public/assets/jimpower/cardbackK.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
demo/public/assets/jimpower/home/bus_10.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
demo/public/assets/jimpower/home/git.png
Normal file
After Width: | Height: | Size: 106 KiB |
BIN
demo/public/assets/jimpower/home/house_4.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
demo/public/assets/jimpower/home/james_10.jpg
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
demo/public/assets/jimpower/home/tina_4.jpg
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
demo/public/assets/jimpower/security/air_8.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
demo/public/assets/jimpower/security/alarm_3.jpg
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
demo/public/assets/jimpower/security/door_3.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
demo/public/assets/jimpower/security/leak_2.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
demo/public/assets/jimpower/security/motion_3.jpg
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
demo/public/assets/jimpower/security/smoke_4.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
demo/public/assets/jimpower/security/window_2.jpg
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
demo/public/assets/kernehed/bella.jpg
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
demo/public/assets/kernehed/camera.entre.jpg
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
demo/public/assets/kernehed/oscar.jpg
Normal file
After Width: | Height: | Size: 25 KiB |
23
demo/public/index.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#2157BC" />
|
||||
<title>Home Assistant Demo</title>
|
||||
<script src="./custom-elements-es5-adapter.js"></script>
|
||||
<script src="./compatibility.js"></script>
|
||||
<script src="./main.js" async></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Roboto, Noto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
17
demo/script/build_demo
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# Build the demo
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
OUTPUT_DIR=dist
|
||||
|
||||
rm -rf $OUTPUT_DIR
|
||||
|
||||
cd ..
|
||||
DEMO=1 ./node_modules/.bin/gulp build-translations gen-icons
|
||||
cd demo
|
||||
|
||||
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js
|
13
demo/script/develop_demo
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# Develop the demo
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
cd ..
|
||||
DEMO=1 ./node_modules/.bin/gulp build-translations gen-icons
|
||||
cd demo
|
||||
|
||||
../node_modules/.bin/webpack-dev-server
|
6
demo/src/auth.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { MockHomeAssistant } from "../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockAuth = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS("config/auth/list", () => []);
|
||||
hass.mockWS("auth/refresh_tokens", () => []);
|
||||
};
|
25
demo/src/configs/demo-configs.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
import { Lovelace } from "../../../src/panels/lovelace/types";
|
||||
import { DemoConfig } from "./types";
|
||||
|
||||
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
||||
() => import("./kernehed").then((mod) => mod.demoKernehed),
|
||||
() => import("./jimpower").then((mod) => mod.demoJimpower),
|
||||
];
|
||||
|
||||
export let selectedDemoConfigIndex: number = 0;
|
||||
export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[
|
||||
selectedDemoConfigIndex
|
||||
]();
|
||||
|
||||
export const setDemoConfig = async (
|
||||
hass: MockHomeAssistant,
|
||||
lovelace: Lovelace,
|
||||
index: number
|
||||
) => {
|
||||
selectedDemoConfigIndex = index;
|
||||
selectedDemoConfig = demoConfigs[index]();
|
||||
const config = await selectedDemoConfig;
|
||||
hass.addEntities(config.entities(), true);
|
||||
lovelace.saveConfig(config.lovelace());
|
||||
};
|
13271
demo/src/configs/jimpower/entities.ts
Normal file
11
demo/src/configs/jimpower/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { DemoConfig } from "../types";
|
||||
import { demoLovelaceJimpower } from "./lovelace";
|
||||
import { demoEntitiesJimpower } from "./entities";
|
||||
|
||||
export const demoJimpower: DemoConfig = {
|
||||
authorName: "Jimpower",
|
||||
authorUrl: " https://github.com/JamesMcCarthy79/Home-Assistant-Config",
|
||||
name: "Kingia Castle",
|
||||
lovelace: demoLovelaceJimpower,
|
||||
entities: demoEntitiesJimpower,
|
||||
};
|
1428
demo/src/configs/jimpower/lovelace.ts
Normal file
8761
demo/src/configs/kernehed/entities.ts
Normal file
11
demo/src/configs/kernehed/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { DemoConfig } from "../types";
|
||||
import { demoLovelaceKernehed } from "./lovelace";
|
||||
import { demoEntitiesKernehed } from "./entities";
|
||||
|
||||
export const demoKernehed: DemoConfig = {
|
||||
authorName: "Kernehed",
|
||||
authorUrl: "",
|
||||
name: "Hem",
|
||||
lovelace: demoLovelaceKernehed,
|
||||
entities: demoEntitiesKernehed,
|
||||
};
|
492
demo/src/configs/kernehed/lovelace.ts
Normal file
@ -0,0 +1,492 @@
|
||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||
|
||||
export const demoLovelaceKernehed: () => LovelaceConfig = () => ({
|
||||
name: "Hem",
|
||||
resources: [
|
||||
// {
|
||||
// url: "/local/custom-lovelace/monster-card.js",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/mini-media-player-bundle.js?v=0.9.8",
|
||||
// type: "module",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/slideshow-card.js?=1.1.0",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/fold-entity-row.js?v=3ae2c4",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/swipe-card/swipe-card.js?v=2.0.0",
|
||||
// type: "module",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/upcoming-media-card/upcoming-media-card.js",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/tracker-card.js?v=0.1.5",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/card-tools.js?v=6ce5d0",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/krisinfo.js?=0.0.1",
|
||||
// type: "js",
|
||||
// },
|
||||
],
|
||||
views: [
|
||||
{
|
||||
cards: [
|
||||
{ type: "custom:ha-demo-card" },
|
||||
{
|
||||
cards: [
|
||||
{
|
||||
cards: [
|
||||
{
|
||||
image: "/assets/kernehed/oscar.jpg",
|
||||
elements: [
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "93%",
|
||||
left: "20%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.oskar_devices",
|
||||
},
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "93%",
|
||||
left: "90%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.battery_oskar",
|
||||
},
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "93%",
|
||||
left: "55%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.oskar_tid_till_hem",
|
||||
},
|
||||
],
|
||||
type: "picture-elements",
|
||||
},
|
||||
{
|
||||
image: "/assets/kernehed/bella.jpg",
|
||||
elements: [
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "92%",
|
||||
left: "20%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.bella_devices",
|
||||
},
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "92%",
|
||||
left: "90%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.battery_bella",
|
||||
},
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "92%",
|
||||
left: "55%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.bella_tid_till_hem",
|
||||
},
|
||||
],
|
||||
type: "picture-elements",
|
||||
},
|
||||
],
|
||||
type: "horizontal-stack",
|
||||
},
|
||||
],
|
||||
type: "vertical-stack",
|
||||
id: "4db5c4664f0a4458949aec3651e4d7a6",
|
||||
},
|
||||
{
|
||||
entities: [
|
||||
"lock.polycontrol_danalock_v3_btze_locked",
|
||||
"sensor.zwave_battery_front_door",
|
||||
"alarm_control_panel.kernehed_manison",
|
||||
"binary_sensor.dorrklockan",
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
id: "37279816181f442eac853b03c0473101",
|
||||
title: "L\u00e5set",
|
||||
},
|
||||
// {
|
||||
// filter: {
|
||||
// exclude: [
|
||||
// {
|
||||
// state: "not_home",
|
||||
// },
|
||||
// ],
|
||||
// include: [
|
||||
// {
|
||||
// entity_id: "device_tracker.annasiphone",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "device_tracker.iphone_2",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// type: "custom:monster-card",
|
||||
// id: "6d4744d14a7c42668633cedbe655ba08",
|
||||
// card: {
|
||||
// show_header_toggle: false,
|
||||
// type: "entities",
|
||||
// title: "G\u00e4ster",
|
||||
// },
|
||||
// show_empty: false,
|
||||
// },
|
||||
// {
|
||||
// filter: {
|
||||
// exclude: [
|
||||
// {
|
||||
// state: "Inget",
|
||||
// },
|
||||
// {
|
||||
// state: "i.u.",
|
||||
// },
|
||||
// ],
|
||||
// include: [
|
||||
// {
|
||||
// entity_id: "sensor.pollen_al",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_alm",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_salg_vide",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_bjork",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_bok",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_ek",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_grabo",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_gras",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_hassel",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// type: "custom:monster-card",
|
||||
// id: "7ecee83212d340b0901f63ac9ec24328",
|
||||
// card: {
|
||||
// show_header_toggle: false,
|
||||
// type: "entities",
|
||||
// title: "Pollenniv\u00e5er",
|
||||
// },
|
||||
// show_empty: false,
|
||||
// },
|
||||
{
|
||||
cards: [
|
||||
{
|
||||
entities: [
|
||||
"switch.rest_julbelysning",
|
||||
"binary_sensor.front_door_sensor",
|
||||
"binary_sensor.unifi_camera",
|
||||
"binary_sensor.back_door_sensor",
|
||||
],
|
||||
image: "/assets/kernehed/camera.entre.jpg",
|
||||
type: "picture-glance",
|
||||
title: "Entr\u00e9 kamera",
|
||||
},
|
||||
{
|
||||
entities: [
|
||||
"input_select.christmas_pattern",
|
||||
"input_select.christmas_palette",
|
||||
],
|
||||
type: "entities",
|
||||
},
|
||||
],
|
||||
type: "vertical-stack",
|
||||
id: "fc8abcaade0e4087a10a5602f3bdb4d4",
|
||||
},
|
||||
// {
|
||||
// url: "https://embed.windy.com/embed2.html",
|
||||
// type: "iframe",
|
||||
// id: "3870fdc794274f17b84dd6ced631b737",
|
||||
// },
|
||||
{
|
||||
entities: [
|
||||
{
|
||||
name: "Tv\u00e4ttstugan",
|
||||
entity: "binary_sensor.tvattstugan_motion_sensor",
|
||||
},
|
||||
{
|
||||
name: "Skafferiet",
|
||||
entity: "binary_sensor.skafferiet_motion_sensor",
|
||||
},
|
||||
{
|
||||
name: "K\u00e4llaren",
|
||||
entity: "binary_sensor.kallaren_motion_sensor",
|
||||
},
|
||||
{
|
||||
name: "Trappen",
|
||||
entity: "binary_sensor.trapp_motion_sensor",
|
||||
},
|
||||
{
|
||||
name: "B\u00e4nksensor",
|
||||
entity: "binary_sensor.banksensor",
|
||||
},
|
||||
{
|
||||
name: "Altansensor",
|
||||
entity: "binary_sensor.altan_motion_sensor",
|
||||
},
|
||||
{
|
||||
name: "Badrum",
|
||||
entity: "binary_sensor.badrumssensor",
|
||||
},
|
||||
],
|
||||
type: "glance",
|
||||
id: "fac4c51ac1914e3a897da643077e15f3",
|
||||
show_state: false,
|
||||
},
|
||||
{
|
||||
entities: ["sensor.oskar_bluetooth"],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
id: "37279816181f442eac853b132142141",
|
||||
title: "Rum lokalisering",
|
||||
},
|
||||
// {
|
||||
// filter: {
|
||||
// exclude: [
|
||||
// {
|
||||
// state: false,
|
||||
// },
|
||||
// ],
|
||||
// include: [
|
||||
// {
|
||||
// entity_id:
|
||||
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_2",
|
||||
// },
|
||||
// {
|
||||
// entity_id:
|
||||
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_3",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// type: "custom:monster-card",
|
||||
// id: "2a440c2701824fdb9d5ebc9827c0917b",
|
||||
// card: {
|
||||
// show_header_toggle: false,
|
||||
// type: "entities",
|
||||
// title: "Brandvarnare",
|
||||
// },
|
||||
// show_empty: false,
|
||||
// },
|
||||
{
|
||||
type: "weather-forecast",
|
||||
id: "2bf8ccbc1f664c23b10b6533ae82f7e2",
|
||||
entity: "weather.smhi_vader",
|
||||
},
|
||||
// {
|
||||
// cards: [
|
||||
// {
|
||||
// max: 50,
|
||||
// min: -50,
|
||||
// type: "gauge",
|
||||
// title: "\u00d6verv\u00e5ning",
|
||||
// entity:
|
||||
// "sensor.fibaro_system_unknown_type0c02_id1003_temperature",
|
||||
// },
|
||||
// {
|
||||
// max: 50,
|
||||
// min: -50,
|
||||
// type: "gauge",
|
||||
// title: "Entr\u00e9n",
|
||||
// entity:
|
||||
// "sensor.fibaro_system_unknown_type0c02_id1003_temperature_2",
|
||||
// },
|
||||
// {
|
||||
// max: 50,
|
||||
// min: -50,
|
||||
// type: "gauge",
|
||||
// title: "K\u00e4llaren",
|
||||
// entity:
|
||||
// "sensor.philio_technology_corporation_phpat02beu_multisensor_2in1_temperature",
|
||||
// },
|
||||
// ],
|
||||
// type: "custom:slideshow-card",
|
||||
// arrow_color: "var(--primary-text-color)",
|
||||
// arrow_opacity: 0.7,
|
||||
// },
|
||||
],
|
||||
title: "Hem",
|
||||
id: "hem",
|
||||
icon: "mdi:home",
|
||||
},
|
||||
{
|
||||
cards: [
|
||||
{
|
||||
entities: [
|
||||
"sensor.processor_use",
|
||||
"sensor.memory_free",
|
||||
"sensor.disk_free_home",
|
||||
"sensor.last_boot",
|
||||
"sensor.db_size",
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
id: "7c92cd52219548b6a6a6d5ee6088e071",
|
||||
title: "System",
|
||||
},
|
||||
{
|
||||
entities: [
|
||||
"sensor.pi_hole_dns_queries_today",
|
||||
"sensor.pi_hole_ads_blocked_today",
|
||||
"sensor.pi_hole_dns_unique_clients",
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
id: 123123123123213123,
|
||||
title: "Pi-Hole",
|
||||
},
|
||||
{
|
||||
entities: [
|
||||
"sensor.plex",
|
||||
"binary_sensor.gaming_pc",
|
||||
"binary_sensor.server_1",
|
||||
"binary_sensor.server_2",
|
||||
"binary_sensor.windows_server",
|
||||
"binary_sensor.teamspeak",
|
||||
"binary_sensor.harmony_hub",
|
||||
{
|
||||
style: {
|
||||
height: "1px",
|
||||
width: "85%",
|
||||
"margin-left": "auto",
|
||||
background: "#62717b",
|
||||
"margin-right": "auto",
|
||||
},
|
||||
type: "divider",
|
||||
},
|
||||
// {
|
||||
// items: ["sensor.uptime_router", "sensor.installerad_routeros"],
|
||||
// head: {
|
||||
// entity: "binary_sensor.router",
|
||||
// },
|
||||
// type: "custom:fold-entity-row",
|
||||
// group_config: {
|
||||
// icon: "mdi:router",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// items: [
|
||||
// "sensor.uptime_router_server",
|
||||
// "sensor.installerad_routeros_server",
|
||||
// ],
|
||||
// head: {
|
||||
// entity: "binary_sensor.router_server",
|
||||
// },
|
||||
// type: "custom:fold-entity-row",
|
||||
// group_config: {
|
||||
// icon: "mdi:router",
|
||||
// },
|
||||
// },
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
id: "3e18f63e2c6640d185bf0486a9c4c03f",
|
||||
title: "N\u00e4tverk",
|
||||
},
|
||||
{
|
||||
entities: [
|
||||
"binary_sensor.ubiquiti_controller",
|
||||
"binary_sensor.ubiquiti_switch",
|
||||
"binary_sensor.ubiquiti_nvr",
|
||||
"binary_sensor.entre_kamera",
|
||||
// {
|
||||
// items: ["sensor.uptime_ap_1"],
|
||||
// head: {
|
||||
// entity: "binary_sensor.accesspunkt_1",
|
||||
// },
|
||||
// type: "custom:fold-entity-row",
|
||||
// group_config: {
|
||||
// icon: "router-wireless",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// items: ["sensor.uptime_ap_2"],
|
||||
// head: {
|
||||
// entity: "binary_sensor.accesspunkt_2",
|
||||
// },
|
||||
// type: "custom:fold-entity-row",
|
||||
// group_config: {
|
||||
// icon: "router-wireless",
|
||||
// },
|
||||
// },
|
||||
"sensor.total_clients_wireless",
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
id: "b8e18e8750224f58b404d0f2e644529a",
|
||||
title: "Ubiquiti",
|
||||
},
|
||||
{
|
||||
entities: [
|
||||
"sensor.qbittorrent_up_speed",
|
||||
"sensor.qbittorrent_down_speed",
|
||||
"sensor.qbittorrent_status",
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
id: "af8fb9251ce7453ca90c710722b4625b",
|
||||
title: "Bittorrent",
|
||||
},
|
||||
{
|
||||
entities: [
|
||||
"sensor.speedtest_download",
|
||||
"sensor.speedtest_upload",
|
||||
"sensor.speedtest_ping",
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
id: 12312412,
|
||||
title: "Bandbredd",
|
||||
},
|
||||
// {
|
||||
// title: "Updater",
|
||||
// type: "custom:tracker-card",
|
||||
// trackers: [
|
||||
// "sensor.custom_card_tracker",
|
||||
// "sensor.custom_component_tracker",
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
title: "System & N\u00e4tverk",
|
||||
id: "system_natverk",
|
||||
icon: "mdi:server-network",
|
||||
},
|
||||
],
|
||||
});
|
11
demo/src/configs/types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { LovelaceConfig } from "../../../src/data/lovelace";
|
||||
import { Entity } from "../../../src/fake_data/entity";
|
||||
|
||||
export interface DemoConfig {
|
||||
index?: number;
|
||||
name: string;
|
||||
authorName: string;
|
||||
authorUrl: string;
|
||||
lovelace: () => LovelaceConfig;
|
||||
entities: () => Entity[];
|
||||
}
|
86
demo/src/custom-cards/card-modder.js
Normal file
@ -0,0 +1,86 @@
|
||||
import { LitElement } from "lit-element";
|
||||
import "./card-tools";
|
||||
|
||||
class CardModder extends LitElement {
|
||||
setConfig(config) {
|
||||
if (!window.cardTools)
|
||||
throw new Error(
|
||||
`Can't find card-tools. See https://github.com/thomasloven/lovelace-card-tools`
|
||||
);
|
||||
window.cardTools.checkVersion(0.2);
|
||||
|
||||
if (!config || !config.card) {
|
||||
throw new Error("Card config incorrect");
|
||||
}
|
||||
if (Array.isArray(config.card)) {
|
||||
throw new Error("It says 'card', not 'cardS'. Remove the dash.");
|
||||
}
|
||||
this._config = config;
|
||||
this.card = window.cardTools.createCard(config.card);
|
||||
this.templated = [];
|
||||
this.attempts = 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
return window.cardTools.litHtml`
|
||||
<div id="root">${this.card}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this._cardMod();
|
||||
}
|
||||
|
||||
_cardMod() {
|
||||
if (!this._config.style) return;
|
||||
|
||||
let target = null;
|
||||
target = target || this.card.querySelector("ha-card");
|
||||
target =
|
||||
target ||
|
||||
(this.card.shadowRoot && this.card.shadowRoot.querySelector("ha-card"));
|
||||
target =
|
||||
target ||
|
||||
(this.card.firstChild &&
|
||||
this.card.firstChild.shadowRoot &&
|
||||
this.card.firstChild.shadowRoot.querySelector("ha-card"));
|
||||
if (!target && !this.attempts)
|
||||
// Try twice
|
||||
setTimeout(() => this._cardMod(), 100);
|
||||
this.attempts++;
|
||||
target = target || this.card;
|
||||
|
||||
for (var k in this._config.style) {
|
||||
if (window.cardTools.hasTemplate(this._config.style[k]))
|
||||
this.templated.push(k);
|
||||
target.style.setProperty(
|
||||
k,
|
||||
window.cardTools.parseTemplate(this._config.style[k])
|
||||
);
|
||||
}
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
set hass(hass) {
|
||||
if (this.card) this.card.hass = hass;
|
||||
if (this.templated)
|
||||
this.templated.forEach((k) => {
|
||||
this.target.style.setProperty(
|
||||
k,
|
||||
window.cardTools.parseTemplate(this._config.style[k], "")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getCardSize() {
|
||||
if (this._config && this._config.report_size)
|
||||
return this._config.report_size;
|
||||
if (this.card)
|
||||
return typeof this.card.getCardSize === "function"
|
||||
? this.card.getCardSize()
|
||||
: 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("card-modder", CardModder);
|
197
demo/src/custom-cards/card-tools.js
Normal file
@ -0,0 +1,197 @@
|
||||
import { LitElement, html } from "lit-element";
|
||||
|
||||
if (!window.cardTools) {
|
||||
const version = 0.2;
|
||||
const CUSTOM_TYPE_PREFIX = "custom:";
|
||||
|
||||
let cardTools = {};
|
||||
|
||||
cardTools.v = version;
|
||||
|
||||
cardTools.checkVersion = (v) => {
|
||||
if (version < v) {
|
||||
throw new Error(
|
||||
`Old version of card-tools found. Get the latest version of card-tools.js from https://github.com/thomasloven/lovelace-card-tools`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
cardTools.LitElement = LitElement;
|
||||
|
||||
cardTools.litHtml = html;
|
||||
|
||||
cardTools.hass = () => {
|
||||
return document.querySelector("home-assistant").hass;
|
||||
};
|
||||
|
||||
cardTools.fireEvent = (ev, detail) => {
|
||||
ev = new Event(ev, {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: true,
|
||||
});
|
||||
ev.detail = detail || {};
|
||||
document.querySelector("ha-demo").dispatchEvent(ev);
|
||||
};
|
||||
|
||||
cardTools.createThing = (thing, config) => {
|
||||
const _createThing = (tag, config) => {
|
||||
const element = document.createElement(tag);
|
||||
try {
|
||||
element.setConfig(config);
|
||||
} catch (err) {
|
||||
console.error(tag, err);
|
||||
return _createError(err.message, config);
|
||||
}
|
||||
return element;
|
||||
};
|
||||
|
||||
const _createError = (error, config) => {
|
||||
return _createThing("hui-error-card", {
|
||||
type: "error",
|
||||
error,
|
||||
config,
|
||||
});
|
||||
};
|
||||
|
||||
if (!config || typeof config !== "object" || !config.type)
|
||||
return _createError(`No ${thing} type configured`, config);
|
||||
let tag = config.type;
|
||||
if (config.error) {
|
||||
const err = config.error;
|
||||
delete config.error;
|
||||
return _createError(err, config);
|
||||
}
|
||||
if (tag.startsWith(CUSTOM_TYPE_PREFIX))
|
||||
tag = tag.substr(CUSTOM_TYPE_PREFIX.length);
|
||||
else tag = `hui-${tag}-${thing}`;
|
||||
|
||||
if (customElements.get(tag)) return _createThing(tag, config);
|
||||
|
||||
// If element doesn't exist (yet) create an error
|
||||
const element = _createError(
|
||||
`Custom element doesn't exist: ${tag}.`,
|
||||
config
|
||||
);
|
||||
element.style.display = "None";
|
||||
const time = setTimeout(() => {
|
||||
element.style.display = "";
|
||||
}, 2000);
|
||||
// Remove error if element is defined later
|
||||
customElements.whenDefined(tag).then(() => {
|
||||
clearTimeout(timer);
|
||||
cardTools.fireEvent("rebuild-view");
|
||||
});
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
cardTools.createCard = (config) => {
|
||||
return cardTools.createThing("card", config);
|
||||
};
|
||||
|
||||
cardTools.createElement = (config) => {
|
||||
return cardTools.createThing("element", config);
|
||||
};
|
||||
|
||||
cardTools.createEntityRow = (config) => {
|
||||
const SPECIAL_TYPES = new Set([
|
||||
"call-service",
|
||||
"divider",
|
||||
"section",
|
||||
"weblink",
|
||||
]);
|
||||
const DEFAULT_ROWS = {
|
||||
alert: "toggle",
|
||||
automation: "toggle",
|
||||
climate: "toggle",
|
||||
cover: "cover",
|
||||
fan: "toggle",
|
||||
group: "group",
|
||||
input_boolean: "toggle",
|
||||
input_number: "input-number",
|
||||
input_select: "input-select",
|
||||
input_text: "input-text",
|
||||
light: "toggle",
|
||||
media_player: "media-player",
|
||||
lock: "lock",
|
||||
scene: "scene",
|
||||
script: "script",
|
||||
sensor: "sensor",
|
||||
timer: "timer",
|
||||
switch: "toggle",
|
||||
vacuum: "toggle",
|
||||
};
|
||||
|
||||
if (
|
||||
!config ||
|
||||
typeof config !== "object" ||
|
||||
(!config.entity && !config.type)
|
||||
) {
|
||||
Object.assign(config, { error: "Invalid config given" });
|
||||
return cardTools.createThing("", config);
|
||||
}
|
||||
|
||||
const type = config.type || "default";
|
||||
if (SPECIAL_TYPES.has(type) || type.startsWith(CUSTOM_TYPE_PREFIX))
|
||||
return cardTools.createThing("row", config);
|
||||
|
||||
const domain = config.entity.split(".", 1)[0];
|
||||
Object.assign(config, { type: DEFAULT_ROWS[domain] || "text" });
|
||||
return cardTools.createThing("entity-row", config);
|
||||
};
|
||||
|
||||
cardTools.deviceID = (() => {
|
||||
const ID_STORAGE_KEY = "lovelace-player-device-id";
|
||||
if (window["fully"] && typeof fully.getDeviceId === "function")
|
||||
return fully.getDeviceId();
|
||||
if (!localStorage[ID_STORAGE_KEY]) {
|
||||
const s4 = () => {
|
||||
return Math.floor((1 + Math.random()) * 100000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
};
|
||||
localStorage[ID_STORAGE_KEY] = `${s4()}${s4()}-${s4()}${s4()}`;
|
||||
}
|
||||
return localStorage[ID_STORAGE_KEY];
|
||||
})();
|
||||
|
||||
cardTools.moreInfo = (entity) => {
|
||||
cardTools.fireEvent("hass-more-info", { entityId: entity });
|
||||
};
|
||||
|
||||
cardTools.longpress = (element) => {
|
||||
customElements.whenDefined("long-press").then(() => {
|
||||
const longpress = document.body.querySelector("long-press");
|
||||
longpress.bind(element);
|
||||
});
|
||||
return element;
|
||||
};
|
||||
|
||||
cardTools.hasTemplate = (text) => {
|
||||
return /\[\[\s+.*\s+\]\]/.test(text);
|
||||
};
|
||||
|
||||
cardTools.parseTemplate = (text, error) => {
|
||||
const _parse = (str) => {
|
||||
try {
|
||||
str = str.replace(/^\[\[\s+|\s+\]\]$/g, "");
|
||||
const parts = str.split(".");
|
||||
let v = cardTools.hass().states[`${parts[0]}.${parts[1]}`];
|
||||
parts.shift();
|
||||
parts.shift();
|
||||
parts.forEach((item) => (v = v[item]));
|
||||
return v;
|
||||
} catch (err) {
|
||||
return error || `[[ Template matching failed ${str} ]]`;
|
||||
}
|
||||
};
|
||||
text = text.replace(/(\[\[\s.*?\s\]\])/g, (str, p1, offset, s) =>
|
||||
_parse(str)
|
||||
);
|
||||
return text;
|
||||
};
|
||||
|
||||
window.cardTools = cardTools;
|
||||
cardTools.fireEvent("rebuild-view");
|
||||
}
|
58
demo/src/entities.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { getEntity } from "../../src/fake_data/entity";
|
||||
|
||||
export const entities = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Light",
|
||||
}),
|
||||
getEntity("group", "kitchen", "on", {
|
||||
entity_id: ["light.bed_light"],
|
||||
order: 8,
|
||||
friendly_name: "Kitchen",
|
||||
}),
|
||||
getEntity("lock", "kitchen_door", "locked", {
|
||||
friendly_name: "Kitchen Door",
|
||||
}),
|
||||
getEntity("cover", "kitchen_window", "open", {
|
||||
friendly_name: "Kitchen Window",
|
||||
supported_features: 11,
|
||||
}),
|
||||
getEntity("scene", "romantic_lights", "scening", {
|
||||
entity_id: ["light.bed_light", "light.ceiling_lights"],
|
||||
friendly_name: "Romantic lights",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_paulus", "home", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: "Paulus",
|
||||
}),
|
||||
getEntity("climate", "ecobee", "auto", {
|
||||
current_temperature: 73,
|
||||
min_temp: 45,
|
||||
max_temp: 95,
|
||||
temperature: null,
|
||||
target_temp_high: 75,
|
||||
target_temp_low: 70,
|
||||
fan_mode: "Auto Low",
|
||||
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
|
||||
operation_mode: "auto",
|
||||
operation_list: ["heat", "cool", "auto", "off"],
|
||||
hold_mode: "home",
|
||||
swing_mode: "Auto",
|
||||
swing_list: ["Auto", "1", "2", "3", "Off"],
|
||||
unit_of_measurement: "°F",
|
||||
friendly_name: "Ecobee",
|
||||
supported_features: 1014,
|
||||
}),
|
||||
getEntity("input_number", "noise_allowance", 5, {
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
mode: "slider",
|
||||
unit_of_measurement: "dB",
|
||||
friendly_name: "Allowed Noise",
|
||||
icon: "mdi:bell-ring",
|
||||
}),
|
||||
];
|
19
demo/src/entrypoint.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import "@polymer/paper-styles/typography";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
|
||||
import "../../src/resources/hass-icons";
|
||||
import "../../src/resources/ha-style";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/components/ha-iconset-svg";
|
||||
|
||||
import "./ha-demo";
|
||||
|
||||
/* polyfill for paper-dropdown */
|
||||
setTimeout(
|
||||
() =>
|
||||
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"),
|
||||
1000
|
||||
);
|
||||
|
||||
document.body.appendChild(document.createElement("ha-demo"));
|
139
demo/src/ha-demo-card.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { LitElement, html, CSSResult, css } from "lit-element";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import "@polymer/paper-icon-button";
|
||||
import "../../src/components/ha-card";
|
||||
import { LovelaceCard, Lovelace } from "../../src/panels/lovelace/types";
|
||||
import { LovelaceCardConfig } from "../../src/data/lovelace";
|
||||
import { MockHomeAssistant } from "../../src/fake_data/provide_hass";
|
||||
import {
|
||||
demoConfigs,
|
||||
selectedDemoConfig,
|
||||
setDemoConfig,
|
||||
selectedDemoConfigIndex,
|
||||
} from "./configs/demo-configs";
|
||||
|
||||
export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
public lovelace?: Lovelace;
|
||||
public hass?: MockHomeAssistant;
|
||||
|
||||
public getCardSize() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public setConfig(
|
||||
// @ts-ignore
|
||||
config: LovelaceCardConfig
|
||||
// tslint:disable-next-line:no-empty
|
||||
) {}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card header="Home Assistant Demo Switcher">
|
||||
<div class="picker">
|
||||
<paper-icon-button
|
||||
@click=${this._prevConfig}
|
||||
icon="hass:chevron-right"
|
||||
style="transform: rotate(180deg)"
|
||||
></paper-icon-button>
|
||||
<div>
|
||||
${
|
||||
until(
|
||||
selectedDemoConfig.then(
|
||||
(conf) => html`
|
||||
${conf.name}
|
||||
<small>
|
||||
by
|
||||
<a target="_blank" href="${conf.authorUrl}">
|
||||
${conf.authorName}
|
||||
</a>
|
||||
</small>
|
||||
`
|
||||
),
|
||||
""
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<paper-icon-button
|
||||
@click=${this._nextConfig}
|
||||
icon="hass:chevron-right"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _prevConfig() {
|
||||
this._updateConfig(
|
||||
selectedDemoConfigIndex > 0
|
||||
? selectedDemoConfigIndex - 1
|
||||
: demoConfigs.length - 1
|
||||
);
|
||||
}
|
||||
|
||||
private _nextConfig() {
|
||||
this._updateConfig(
|
||||
selectedDemoConfigIndex < demoConfigs.length - 1
|
||||
? selectedDemoConfigIndex + 1
|
||||
: 0
|
||||
);
|
||||
}
|
||||
|
||||
private _updateConfig(index: number) {
|
||||
setDemoConfig(this.hass!, this.lovelace!, index);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
.content {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 16px 16px 16px 38px;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.picker div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.picker small {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-demo-card": HADemoCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-demo-card", HADemoCard);
|
72
demo/src/ha-demo.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { HomeAssistant } from "../../src/layouts/app/home-assistant";
|
||||
import { provideHass } from "../../src/fake_data/provide_hass";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import { mockLovelace } from "./lovelace";
|
||||
import { mockAuth } from "./auth";
|
||||
import { selectedDemoConfig } from "./configs/demo-configs";
|
||||
|
||||
class HaDemo extends HomeAssistant {
|
||||
protected async _handleConnProm() {
|
||||
const hass = provideHass(this, {
|
||||
panelUrl: (this as any).panelUrl,
|
||||
});
|
||||
mockLovelace(hass);
|
||||
mockAuth(hass);
|
||||
selectedDemoConfig.then((conf) => hass.addEntities(conf.entities()));
|
||||
|
||||
// Taken from polymer/pwa-helpers. BSD-3 licensed
|
||||
document.body.addEventListener(
|
||||
"click",
|
||||
(e) => {
|
||||
if (
|
||||
e.defaultPrevented ||
|
||||
e.button !== 0 ||
|
||||
e.metaKey ||
|
||||
e.ctrlKey ||
|
||||
e.shiftKey
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const anchor = e
|
||||
.composedPath()
|
||||
.filter((n) => (n as HTMLElement).tagName === "A")[0] as
|
||||
| HTMLAnchorElement
|
||||
| undefined;
|
||||
if (
|
||||
!anchor ||
|
||||
anchor.target ||
|
||||
anchor.hasAttribute("download") ||
|
||||
anchor.getAttribute("rel") === "external"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let href = anchor.href;
|
||||
if (!href || href.indexOf("mailto:") !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const location = window.location;
|
||||
const origin =
|
||||
location.origin || location.protocol + "//" + location.host;
|
||||
if (href.indexOf(origin) !== 0) {
|
||||
return;
|
||||
}
|
||||
href = href.substr(origin.length);
|
||||
|
||||
if (href === "#") {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
navigate(this as any, href);
|
||||
},
|
||||
{ capture: true }
|
||||
);
|
||||
|
||||
(this as any).hassConnected();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-demo", HaDemo);
|
31
demo/src/lovelace.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { entities } from "./entities";
|
||||
|
||||
import "./ha-demo-card";
|
||||
// Not duplicate, one is for typing.
|
||||
// tslint:disable-next-line
|
||||
import { HADemoCard } from "./ha-demo-card";
|
||||
import { MockHomeAssistant } from "../../src/fake_data/provide_hass";
|
||||
import { HUIView } from "../../src/panels/lovelace/hui-view";
|
||||
import { selectedDemoConfig } from "./configs/demo-configs";
|
||||
|
||||
export const mockLovelace = (hass: MockHomeAssistant) => {
|
||||
hass.addEntities(entities);
|
||||
|
||||
hass.mockWS("lovelace/config", () =>
|
||||
selectedDemoConfig.then((config) => config.lovelace())
|
||||
);
|
||||
|
||||
hass.mockWS("frontend/get_translations", () => Promise.resolve({}));
|
||||
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
||||
};
|
||||
|
||||
// Patch HUI-VIEW to make the lovelace object available to the demo card
|
||||
const oldCreateCard = HUIView.prototype.createCardElement;
|
||||
|
||||
HUIView.prototype.createCardElement = function(config) {
|
||||
const el = oldCreateCard.call(this, config);
|
||||
if (el.tagName === "HA-DEMO-CARD") {
|
||||
(el as HADemoCard).lovelace = this.lovelace;
|
||||
}
|
||||
return el;
|
||||
};
|
100
demo/webpack.config.js
Normal file
@ -0,0 +1,100 @@
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const { babelLoaderConfig } = require("../config/babel.js");
|
||||
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||
const buildPath = path.resolve(__dirname, "dist");
|
||||
const publicPath = isProd ? "./" : "http://localhost:8080/";
|
||||
|
||||
const latestBuild = false;
|
||||
|
||||
module.exports = {
|
||||
mode: isProd ? "production" : "development",
|
||||
// Disabled in prod while we make Home Assistant able to serve the right files.
|
||||
// Was source-map
|
||||
devtool: isProd ? "none" : "inline-source-map",
|
||||
entry: {
|
||||
main: "./src/entrypoint.ts",
|
||||
compatibility: "../src/entrypoints/compatibility.js",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfig({ latestBuild }),
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
},
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: false,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify("DEMO"),
|
||||
__DEMO__: true,
|
||||
__STATIC_PATH__: "/static/",
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProd ? "production" : "development"
|
||||
),
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
"public",
|
||||
"../node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js",
|
||||
{ from: "../public", to: "static" },
|
||||
{ from: "../build-translations/output", to: "static/translations" },
|
||||
{
|
||||
from: "../node_modules/leaflet/dist/leaflet.css",
|
||||
to: "static/images/leaflet/",
|
||||
},
|
||||
{
|
||||
from: "../node_modules/@polymer/font-roboto-local/fonts",
|
||||
to: "static/fonts",
|
||||
},
|
||||
{
|
||||
from: "../node_modules/leaflet/dist/images",
|
||||
to: "static/images/leaflet/",
|
||||
},
|
||||
]),
|
||||
isProd &&
|
||||
new UglifyJsPlugin({
|
||||
extractComments: true,
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
// Disabling because it broke output
|
||||
mangle: false,
|
||||
},
|
||||
}),
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".json"],
|
||||
alias: {
|
||||
react: "preact-compat",
|
||||
"react-dom": "preact-compat",
|
||||
// Not necessary unless you consume a module using `createClass`
|
||||
"create-react-class": "preact-compat/lib/create-react-class",
|
||||
// Not necessary unless you consume a module requiring `react-dom-factories`
|
||||
"react-dom-factories": "preact-compat/lib/react-dom-factories",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
chunkFilename: chunkFilename,
|
||||
path: buildPath,
|
||||
publicPath,
|
||||
},
|
||||
devServer: {
|
||||
contentBase: "./public",
|
||||
},
|
||||
};
|
13
demo_data/bootstrap_data.js
vendored
@ -1,13 +0,0 @@
|
||||
import config from './config_data';
|
||||
import events from './event_data';
|
||||
import services from './service_data';
|
||||
import states from './state_data';
|
||||
import panels from './panel_data';
|
||||
|
||||
export default {
|
||||
config,
|
||||
events,
|
||||
panels,
|
||||
services,
|
||||
states,
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
export default {
|
||||
components: [
|
||||
'configurator',
|
||||
'http',
|
||||
'api',
|
||||
'frontend',
|
||||
'history',
|
||||
'conversation',
|
||||
'logbook',
|
||||
'introduction',
|
||||
],
|
||||
latitude: 32.87336,
|
||||
location_name: 'Home',
|
||||
longitude: -117.22743,
|
||||
temperature_unit: '\u00b0F',
|
||||
time_zone: 'America/Los_Angeles',
|
||||
version: '0.26',
|
||||
};
|
@ -1,5 +0,0 @@
|
||||
import bootstrap from './bootstrap_data';
|
||||
import logbook from './logbook_data';
|
||||
import stateHistory from './state_history_data';
|
||||
|
||||
window.hassDemoData = { bootstrap, logbook, stateHistory };
|
@ -1,18 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
event: 'call_service',
|
||||
listener_count: 1,
|
||||
},
|
||||
{
|
||||
event: 'time_changed',
|
||||
listener_count: 1,
|
||||
},
|
||||
{
|
||||
event: 'state_changed',
|
||||
listener_count: 3,
|
||||
},
|
||||
{
|
||||
event: 'homeassistant_stop',
|
||||
listener_count: 2,
|
||||
},
|
||||
];
|
@ -1,93 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
domain: 'sun',
|
||||
entity_id: 'sun.sun',
|
||||
message: 'has risen',
|
||||
name: 'sun',
|
||||
when: '2015-04-24T06:08:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'device_tracker',
|
||||
entity_id: 'device_tracker.paulus',
|
||||
message: 'left home',
|
||||
name: 'Paulus',
|
||||
when: '2015-04-24T08:54:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'device_tracker',
|
||||
entity_id: 'device_tracker.anne_therese',
|
||||
message: 'left home',
|
||||
name: 'Anne Therese',
|
||||
when: '2015-04-24T09:08:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'group',
|
||||
entity_id: 'group.all_devices',
|
||||
message: 'left home',
|
||||
name: 'All devices',
|
||||
when: '2015-04-24T09:08:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'thermostat',
|
||||
entity_id: 'thermostat.nest',
|
||||
message: 'changed to 17 \u00b0C',
|
||||
name: 'Nest',
|
||||
when: '2015-04-24T09:08:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'thermostat',
|
||||
entity_id: 'thermostat.nest',
|
||||
message: 'changed to 21 \u00b0C',
|
||||
name: 'Nest',
|
||||
when: '2015-04-24T16:00:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'device_tracker',
|
||||
entity_id: 'device_tracker.anne_therese',
|
||||
message: 'came home',
|
||||
name: 'Anne Therese',
|
||||
when: '2015-04-24T16:24:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'group',
|
||||
entity_id: 'group.all_devices',
|
||||
message: 'came home',
|
||||
name: 'All devices',
|
||||
when: '2015-04-24T16:24:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'light',
|
||||
entity_id: 'light.bowl',
|
||||
message: 'turned on',
|
||||
name: 'Bowl',
|
||||
when: '2015-04-24T18:01:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'light',
|
||||
entity_id: 'light.ceiling',
|
||||
message: 'turned on',
|
||||
name: 'Ceiling',
|
||||
when: '2015-04-24T18:16:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'light',
|
||||
entity_id: 'light.tv_back_light',
|
||||
message: 'turned on',
|
||||
name: 'TV Back Light',
|
||||
when: '2015-04-24T18:31:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'sun',
|
||||
entity_id: 'sun.sun',
|
||||
message: 'has set',
|
||||
name: 'sun',
|
||||
when: '2015-04-24T18:46:47.000Z',
|
||||
},
|
||||
{
|
||||
domain: 'media_player',
|
||||
entity_id: 'media_player.living_room',
|
||||
message: 'changed to Plex',
|
||||
name: 'Media Player',
|
||||
when: '2015-04-24T19:12:47.000Z',
|
||||
},
|
||||
];
|
@ -1,48 +0,0 @@
|
||||
export default {
|
||||
'dev-event': {
|
||||
component_name: 'dev-event',
|
||||
url: '/demo/panels/ha-panel-dev-event.html',
|
||||
url_name: 'dev-event',
|
||||
},
|
||||
'dev-info': {
|
||||
component_name: 'dev-info',
|
||||
url: '/demo/panels/ha-panel-dev-info.html',
|
||||
url_name: 'dev-info',
|
||||
},
|
||||
'dev-service': {
|
||||
component_name: 'dev-service',
|
||||
url: '/demo/panels/ha-panel-dev-service.html',
|
||||
url_name: 'dev-service',
|
||||
},
|
||||
'dev-state': {
|
||||
component_name: 'dev-state',
|
||||
url: '/demo/panels/ha-panel-dev-state.html',
|
||||
url_name: 'dev-state',
|
||||
},
|
||||
'dev-template': {
|
||||
component_name: 'dev-template',
|
||||
url: '/demo/panels/ha-panel-dev-template.html',
|
||||
url_name: 'dev-template',
|
||||
},
|
||||
history: {
|
||||
component_name: 'history',
|
||||
icon: 'mdi:poll-box',
|
||||
title: 'History',
|
||||
url: '/demo/panels/ha-panel-history.html',
|
||||
url_name: 'history',
|
||||
},
|
||||
logbook: {
|
||||
component_name: 'logbook',
|
||||
icon: 'mdi:format-list-bulleted-type',
|
||||
title: 'Logbook',
|
||||
url: '/demo/panels/ha-panel-logbook.html',
|
||||
url_name: 'logbook',
|
||||
},
|
||||
map: {
|
||||
component_name: 'map',
|
||||
icon: 'mdi:account-location',
|
||||
title: 'Map',
|
||||
url: '/demo/panels/ha-panel-map.html',
|
||||
url_name: 'map',
|
||||
},
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
domain: 'homeassistant',
|
||||
services: {
|
||||
stop: { description: '', fields: {} },
|
||||
turn_off: { description: '', fields: {} },
|
||||
turn_on: { description: '', fields: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
domain: 'light',
|
||||
services: {
|
||||
turn_off: { description: '', fields: {} },
|
||||
turn_on: { description: '', fields: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
domain: 'switch',
|
||||
services: {
|
||||
turn_off: { description: '', fields: {} },
|
||||
turn_on: { description: '', fields: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
domain: 'input_boolean',
|
||||
services: {
|
||||
turn_off: { description: '', fields: {} },
|
||||
turn_on: { description: '', fields: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
domain: 'configurator',
|
||||
services: {
|
||||
configure: { description: '', fields: {} },
|
||||
},
|
||||
},
|
||||
];
|
@ -1,279 +0,0 @@
|
||||
function getRandomTime() {
|
||||
const ts = new Date(new Date().getTime() - (Math.random() * 80 * 60 * 1000));
|
||||
return ts.toISOString();
|
||||
}
|
||||
|
||||
const entities = [];
|
||||
|
||||
function addEntity(entityId, state, attributes = {}) {
|
||||
entities.push({
|
||||
state,
|
||||
attributes,
|
||||
entity_id: entityId,
|
||||
last_changed: getRandomTime(),
|
||||
last_updated: getRandomTime(),
|
||||
});
|
||||
}
|
||||
|
||||
let groupOrder = 0;
|
||||
|
||||
function addGroup(objectId, state, entityIds, name, view) {
|
||||
groupOrder++;
|
||||
|
||||
const attributes = {
|
||||
entity_id: entityIds,
|
||||
order: groupOrder,
|
||||
};
|
||||
|
||||
if (name) {
|
||||
attributes.friendly_name = name;
|
||||
}
|
||||
if (view) {
|
||||
attributes.view = view;
|
||||
attributes.hidden = true;
|
||||
}
|
||||
addEntity(`group.${objectId}`, state, attributes);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
// HOME ASSISTANT
|
||||
// ---------------------------------------------------
|
||||
addEntity('a.demo_mode', 'enabled');
|
||||
|
||||
addEntity('configurator.philips_hue', 'configure', {
|
||||
configure_id: '4415244496-1',
|
||||
description: 'Press the button on the bridge to register Philips Hue with Home Assistant.',
|
||||
description_image: '/demo/images/config_philips_hue.jpg',
|
||||
fields: [],
|
||||
submit_caption: 'I have pressed the button',
|
||||
friendly_name: 'Philips Hue',
|
||||
});
|
||||
|
||||
// ---------------------------------------------------
|
||||
// VIEWS
|
||||
// ---------------------------------------------------
|
||||
|
||||
addGroup(
|
||||
'default_view', 'on', [
|
||||
'a.demo_mode',
|
||||
'sensor.humidity',
|
||||
'sensor.temperature',
|
||||
'device_tracker.paulus',
|
||||
'device_tracker.anne_therese',
|
||||
'configurator.philips_hue',
|
||||
'group.cooking',
|
||||
'group.general',
|
||||
'group.rooms',
|
||||
'camera.living_room',
|
||||
'media_player.living_room',
|
||||
'scene.romantic',
|
||||
'scene.good_morning',
|
||||
'script.water_lawn',
|
||||
], 'Main', true);
|
||||
|
||||
addGroup(
|
||||
'rooms_view', 'on', [
|
||||
'group.living_room',
|
||||
'group.bedroom',
|
||||
], 'Rooms', true);
|
||||
|
||||
addGroup('rooms', 'on', ['group.living_room', 'group.bedroom'], 'Rooms');
|
||||
|
||||
// ---------------------------------------------------
|
||||
// DEVICE TRACKER + ZONES
|
||||
// ---------------------------------------------------
|
||||
|
||||
addEntity('device_tracker.anne_therese', 'school', {
|
||||
entity_picture: 'https://graph.facebook.com/621994601/picture',
|
||||
friendly_name: 'Anne Therese',
|
||||
latitude: 32.879898,
|
||||
longitude: -117.236776,
|
||||
gps_accuracy: 250,
|
||||
battery: 76,
|
||||
});
|
||||
|
||||
addEntity('device_tracker.paulus', 'not_home', {
|
||||
entity_picture: 'https://graph.facebook.com/297400035/picture',
|
||||
friendly_name: 'Paulus',
|
||||
gps_accuracy: 75,
|
||||
latitude: 32.892950,
|
||||
longitude: -117.203431,
|
||||
battery: 56,
|
||||
});
|
||||
|
||||
addEntity('zone.school', 'zoning', {
|
||||
radius: 250,
|
||||
latitude: 32.880834,
|
||||
longitude: -117.237556,
|
||||
icon: 'mdi:library',
|
||||
hidden: true,
|
||||
});
|
||||
|
||||
addEntity('zone.work', 'zoning', {
|
||||
radius: 250,
|
||||
latitude: 32.896844,
|
||||
longitude: -117.202204,
|
||||
icon: 'mdi:worker',
|
||||
hidden: true,
|
||||
});
|
||||
|
||||
addEntity('zone.home', 'zoning', {
|
||||
radius: 100,
|
||||
latitude: 32.873708,
|
||||
longitude: -117.226590,
|
||||
icon: 'mdi:home',
|
||||
hidden: true,
|
||||
});
|
||||
|
||||
// ---------------------------------------------------
|
||||
// GENERAL
|
||||
// ---------------------------------------------------
|
||||
addGroup('general', 'on', [
|
||||
'alarm_control_panel.home',
|
||||
'garage_door.garage_door',
|
||||
'lock.kitchen_door',
|
||||
'thermostat.nest',
|
||||
'camera.living_room',
|
||||
]);
|
||||
|
||||
addEntity('camera.living_room', 'idle', {
|
||||
entity_picture: '/demo/webcam.jpg?',
|
||||
});
|
||||
|
||||
addEntity('garage_door.garage_door', 'open', {
|
||||
friendly_name: 'Garage Door',
|
||||
});
|
||||
|
||||
addEntity('alarm_control_panel.home', 'armed_home', {
|
||||
friendly_name: 'Alarm',
|
||||
code_format: '^\\d{4}',
|
||||
});
|
||||
|
||||
addEntity('lock.kitchen_door', 'open', {
|
||||
friendly_name: 'Kitchen Door',
|
||||
});
|
||||
|
||||
// ---------------------------------------------------
|
||||
// PRESETS
|
||||
// ---------------------------------------------------
|
||||
|
||||
addEntity('script.water_lawn', 'off', {
|
||||
friendly_name: 'Water Lawn',
|
||||
});
|
||||
addEntity('scene.romantic', 'scening', {
|
||||
friendly_name: 'Romantic',
|
||||
});
|
||||
// addEntity('scene.good_morning', 'scening', {
|
||||
// friendly_name: 'Good Morning',
|
||||
// });
|
||||
|
||||
// ---------------------------------------------------
|
||||
// LIVING ROOM
|
||||
// ---------------------------------------------------
|
||||
|
||||
addGroup(
|
||||
'living_room', 'on',
|
||||
[
|
||||
'light.table_lamp',
|
||||
'light.ceiling',
|
||||
'light.tv_back_light',
|
||||
'switch.ac',
|
||||
'media_player.living_room',
|
||||
],
|
||||
'Living Room'
|
||||
);
|
||||
|
||||
addEntity('light.tv_back_light', 'off', {
|
||||
friendly_name: 'TV Back Light',
|
||||
});
|
||||
addEntity('light.ceiling', 'on', {
|
||||
friendly_name: 'Ceiling Lights',
|
||||
brightness: 200,
|
||||
rgb_color: [255, 116, 155],
|
||||
});
|
||||
addEntity('light.table_lamp', 'on', {
|
||||
brightness: 200,
|
||||
rgb_color: [150, 212, 94],
|
||||
friendly_name: 'Table Lamp',
|
||||
});
|
||||
addEntity('switch.ac', 'on', {
|
||||
friendly_name: 'AC',
|
||||
icon: 'mdi:air-conditioner',
|
||||
});
|
||||
addEntity('media_player.living_room', 'playing', {
|
||||
entity_picture: '/demo/images/thrones.jpg',
|
||||
friendly_name: 'Chromecast',
|
||||
supported_features: 509,
|
||||
media_content_type: 'tvshow',
|
||||
media_title: 'The Dance of Dragons',
|
||||
media_series_title: 'Game of Thrones',
|
||||
media_season: 5,
|
||||
media_episode: '09',
|
||||
app_name: 'HBO Now',
|
||||
});
|
||||
|
||||
// ---------------------------------------------------
|
||||
// BEDROOM
|
||||
// ---------------------------------------------------
|
||||
|
||||
addGroup(
|
||||
'bedroom', 'off',
|
||||
[
|
||||
'light.bed_light',
|
||||
'switch.decorative_lights',
|
||||
'rollershutter.bedroom_window',
|
||||
],
|
||||
'Bedroom'
|
||||
);
|
||||
|
||||
addEntity('switch.decorative_lights', 'off', {
|
||||
friendly_name: 'Decorative Lights',
|
||||
});
|
||||
addEntity('light.bed_light', 'off', {
|
||||
friendly_name: 'Bed Light',
|
||||
});
|
||||
addEntity('rollershutter.bedroom_window', 'closed', {
|
||||
friendly_name: 'Window',
|
||||
current_position: 0,
|
||||
});
|
||||
|
||||
// ---------------------------------------------------
|
||||
// SENSORS
|
||||
// ---------------------------------------------------
|
||||
|
||||
addEntity('sensor.temperature', '15.6', {
|
||||
unit_of_measurement: '\u00b0C',
|
||||
friendly_name: 'Temperature',
|
||||
});
|
||||
addEntity('sensor.humidity', '54', {
|
||||
unit_of_measurement: '%',
|
||||
friendly_name: 'Humidity',
|
||||
});
|
||||
|
||||
addEntity('thermostat.nest', '23', {
|
||||
away_mode: 'off',
|
||||
temperature: '21',
|
||||
current_temperature: '18',
|
||||
unit_of_measurement: '\u00b0C',
|
||||
friendly_name: 'Nest',
|
||||
});
|
||||
|
||||
// ---------------------------------------------------
|
||||
// COOKING AUTOMATION
|
||||
// ---------------------------------------------------
|
||||
addEntity('input_select.cook_today', 'Paulus', {
|
||||
options: ['Paulus', 'Anne Therese'],
|
||||
icon: 'mdi:panda',
|
||||
});
|
||||
|
||||
addEntity('input_boolean.notify_cook', 'on', {
|
||||
icon: 'mdi:alarm',
|
||||
friendly_name: 'Notify Cook',
|
||||
});
|
||||
|
||||
addGroup(
|
||||
'cooking', 'unknown',
|
||||
['input_select.cook_today', 'input_boolean.notify_cook']
|
||||
);
|
||||
|
||||
export default entities;
|
@ -1,255 +0,0 @@
|
||||
import stateData from './state_data';
|
||||
|
||||
function getTime(minutesAgo) {
|
||||
const ts = new Date(Date.now() - (minutesAgo * 60 * 1000));
|
||||
return ts.toISOString();
|
||||
}
|
||||
|
||||
// prefill with entities we do not want to track
|
||||
const seen = {
|
||||
'a.demo_mode': true,
|
||||
'configurator.philips_hue': true,
|
||||
'group.default_view': true,
|
||||
'group.rooms_view': true,
|
||||
'group.rooms': true,
|
||||
'zone.school': true,
|
||||
'zone.work': true,
|
||||
'zone.home': true,
|
||||
'group.general': true,
|
||||
'camera.roundabout': true,
|
||||
'script.water_lawn': true,
|
||||
'scene.romantic': true,
|
||||
'scene.good_morning': true,
|
||||
'group.cooking': true,
|
||||
};
|
||||
const history = [];
|
||||
|
||||
function randomTimeAdjustment(diff) {
|
||||
return Math.random() * diff - (diff / 2);
|
||||
}
|
||||
|
||||
const maxTime = 1440;
|
||||
|
||||
function addEntity(state, deltas) {
|
||||
seen[state.entity_id] = true;
|
||||
let changes;
|
||||
if (typeof deltas[0] === 'string') {
|
||||
changes = deltas.map(state_ => ({ state: state_ }));
|
||||
} else {
|
||||
changes = deltas;
|
||||
}
|
||||
|
||||
const timeDiff = (900 / changes.length);
|
||||
|
||||
history.push(changes.map(
|
||||
(change, index) => {
|
||||
let attributes;
|
||||
if (!change.attributes && !state.attributes) {
|
||||
attributes = {};
|
||||
} else if (!change.attributes) {
|
||||
attributes = state.attributes;
|
||||
} else if (!state.attributes) {
|
||||
attributes = change.attributes;
|
||||
} else {
|
||||
attributes = Object.assign({}, state.attributes, change.attributes);
|
||||
}
|
||||
|
||||
const time = index === 0 ? getTime(maxTime) : getTime(maxTime - index * timeDiff +
|
||||
randomTimeAdjustment(timeDiff));
|
||||
|
||||
return {
|
||||
attributes,
|
||||
entity_id: state.entity_id,
|
||||
state: change.state || state.state,
|
||||
last_changed: time,
|
||||
last_updated: time,
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'sensor.humidity',
|
||||
attributes: {
|
||||
unit_of_measurement: '%',
|
||||
},
|
||||
}, ['45', '49', '52', '49', '52', '49', '45', '42']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'sensor.temperature',
|
||||
attributes: {
|
||||
unit_of_measurement: '\u00b0C',
|
||||
},
|
||||
}, ['23', '27', '25', '23', '24']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'thermostat.nest',
|
||||
attributes: {
|
||||
unit_of_measurement: '\u00b0C',
|
||||
},
|
||||
}, [
|
||||
{
|
||||
state: '23',
|
||||
attributes: {
|
||||
current_temperature: 20,
|
||||
temperature: 23,
|
||||
},
|
||||
},
|
||||
{
|
||||
state: '23',
|
||||
attributes: {
|
||||
current_temperature: 22,
|
||||
temperature: 23,
|
||||
},
|
||||
},
|
||||
{
|
||||
state: '20',
|
||||
attributes: {
|
||||
current_temperature: 21,
|
||||
temperature: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
state: '20',
|
||||
attributes: {
|
||||
current_temperature: 20,
|
||||
temperature: 20,
|
||||
},
|
||||
},
|
||||
{
|
||||
state: '20',
|
||||
attributes: {
|
||||
current_temperature: 19,
|
||||
temperature: 20,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'media_player.living_room',
|
||||
attributes: {
|
||||
friendly_name: 'Chromecast',
|
||||
},
|
||||
}, ['Plex', 'idle', 'YouTube', 'Netflix', 'idle', 'Plex']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'group.all_devices',
|
||||
}, ['home', 'not_home', 'home']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'device_tracker.paulus',
|
||||
}, ['home', 'not_home', 'work', 'not_home']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'device_tracker.anne_therese',
|
||||
}, ['home', 'not_home', 'home', 'not_home', 'school']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'garage_door.garage_door',
|
||||
}, ['open', 'closed', 'open']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'alarm_control_panel.home',
|
||||
}, ['disarmed', 'pending', 'armed_home', 'pending', 'disarmed', 'pending', 'armed_home']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'lock.kitchen_door',
|
||||
}, ['unlocked', 'locked', 'unlocked', 'locked']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'light.tv_back_light',
|
||||
}, ['on', 'off', 'on', 'off']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'light.ceiling',
|
||||
}, ['on', 'off', 'on']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'light.table_lamp',
|
||||
}, ['on', 'off', 'on']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'switch.ac',
|
||||
}, ['on', 'off', 'on']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'group.bedroom',
|
||||
}, ['on', 'off', 'on', 'off']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'group.living_room',
|
||||
}, ['on', 'off', 'on']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'switch.decorative_lights',
|
||||
}, ['on', 'off', 'on', 'off']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'light.bed_light',
|
||||
}, ['on', 'off', 'on', 'off']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'rollershutter.bedroom_window',
|
||||
}, ['open', 'closed', 'open', 'closed']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'input_select.cook_today',
|
||||
}, ['Anne Therese', 'Paulus']
|
||||
);
|
||||
|
||||
addEntity(
|
||||
{
|
||||
entity_id: 'input_boolean.notify_cook',
|
||||
}, ['off', 'on']
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
for (let i = 0; i < stateData.length; i++) {
|
||||
const entity = stateData[i];
|
||||
if (!(entity.entity_id in seen)) {
|
||||
/* eslint-disable no-console */
|
||||
console.warn(`Missing history for ${entity.entity_id}`);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default history;
|
@ -3,9 +3,9 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import JsYaml from "js-yaml";
|
||||
|
||||
import HomeAssistant from "../data/hass";
|
||||
import { demoConfig } from "../data/demo_config";
|
||||
import { demoServices } from "../data/demo_services";
|
||||
import demoResources from "../data/demo_resources";
|
||||
import { demoConfig } from "../../../src/fake_data/demo_config";
|
||||
import { demoServices } from "../../../src/fake_data/demo_services";
|
||||
import demoResources from "../../../src/fake_data/demo_resources";
|
||||
import demoStates from "../data/demo_states";
|
||||
import { createCardElement } from "../../../src/panels/lovelace/common/create-card-element";
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
export const demoConfig = {
|
||||
elevation: 300,
|
||||
latitude: 51.5287352,
|
||||
longitude: -0.381773,
|
||||
unit_system: {
|
||||
length: "km",
|
||||
mass: "kg",
|
||||
temperature: "°C",
|
||||
volume: "L",
|
||||
},
|
||||
};
|
@ -1,96 +0,0 @@
|
||||
export const demoServices = {
|
||||
configurator: ["configure"],
|
||||
tts: ["demo_say", "clear_cache"],
|
||||
cover: [
|
||||
"open_cover",
|
||||
"close_cover",
|
||||
"open_cover_tilt",
|
||||
"close_cover_tilt",
|
||||
"set_cover_tilt_position",
|
||||
"set_cover_position",
|
||||
"stop_cover_tilt",
|
||||
"stop_cover",
|
||||
],
|
||||
group: ["set", "reload", "remove", "set_visibility"],
|
||||
alarm_control_panel: [
|
||||
"alarm_arm_night",
|
||||
"alarm_disarm",
|
||||
"alarm_trigger",
|
||||
"alarm_arm_home",
|
||||
"alarm_arm_away",
|
||||
"alarm_arm_custom_bypass",
|
||||
],
|
||||
conversation: ["process"],
|
||||
notify: ["demo_test_target_name", "notify"],
|
||||
lock: ["open", "lock", "unlock"],
|
||||
input_select: [
|
||||
"select_previous",
|
||||
"set_options",
|
||||
"select_next",
|
||||
"select_option",
|
||||
],
|
||||
recorder: ["purge"],
|
||||
persistent_notification: ["create", "dismiss"],
|
||||
timer: ["pause", "cancel", "finish", "start"],
|
||||
input_boolean: ["turn_off", "toggle", "turn_on"],
|
||||
fan: [
|
||||
"set_speed",
|
||||
"turn_on",
|
||||
"turn_off",
|
||||
"set_direction",
|
||||
"oscillate",
|
||||
"toggle",
|
||||
],
|
||||
climate: [
|
||||
"set_humidity",
|
||||
"set_operation_mode",
|
||||
"set_aux_heat",
|
||||
"turn_on",
|
||||
"set_hold_mode",
|
||||
"set_away_mode",
|
||||
"turn_off",
|
||||
"set_fan_mode",
|
||||
"set_temperature",
|
||||
"set_swing_mode",
|
||||
],
|
||||
switch: ["turn_off", "toggle", "turn_on"],
|
||||
script: ["turn_off", "demo", "reload", "toggle", "turn_on"],
|
||||
scene: ["turn_on"],
|
||||
system_log: ["clear", "write"],
|
||||
camera: ["disable_motion_detection", "enable_motion_detection", "snapshot"],
|
||||
image_processing: ["scan"],
|
||||
media_player: [
|
||||
"media_previous_track",
|
||||
"clear_playlist",
|
||||
"shuffle_set",
|
||||
"media_seek",
|
||||
"turn_on",
|
||||
"media_play_pause",
|
||||
"media_next_track",
|
||||
"media_pause",
|
||||
"volume_down",
|
||||
"volume_set",
|
||||
"media_stop",
|
||||
"toggle",
|
||||
"media_play",
|
||||
"play_media",
|
||||
"volume_mute",
|
||||
"turn_off",
|
||||
"select_sound_mode",
|
||||
"select_source",
|
||||
"volume_up",
|
||||
],
|
||||
input_number: ["set_value", "increment", "decrement"],
|
||||
device_tracker: ["see"],
|
||||
homeassistant: [
|
||||
"stop",
|
||||
"check_config",
|
||||
"reload_core_config",
|
||||
"turn_on",
|
||||
"turn_off",
|
||||
"restart",
|
||||
"toggle",
|
||||
],
|
||||
light: ["turn_off", "toggle", "turn_on"],
|
||||
input_text: ["set_value"],
|
||||
};
|
@ -1,112 +0,0 @@
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
|
||||
import { demoConfig } from "./demo_config";
|
||||
import { demoServices } from "./demo_services";
|
||||
import demoResources from "./demo_resources";
|
||||
|
||||
const ensureArray = (val) => (Array.isArray(val) ? val : [val]);
|
||||
|
||||
export default (elements, { initialStates = {} } = {}) => {
|
||||
elements = ensureArray(elements);
|
||||
|
||||
const wsCommands = {};
|
||||
const restResponses = {};
|
||||
let hass;
|
||||
const entities = {};
|
||||
|
||||
function updateHass(obj) {
|
||||
hass = Object.assign({}, hass, obj);
|
||||
elements.forEach((el) => {
|
||||
el.hass = hass;
|
||||
});
|
||||
}
|
||||
|
||||
updateHass({
|
||||
// Home Assistant properties
|
||||
config: demoConfig,
|
||||
services: demoServices,
|
||||
language: "en",
|
||||
resources: demoResources,
|
||||
states: initialStates,
|
||||
themes: {},
|
||||
connection: {
|
||||
subscribeEvents: async (callback, event) => {
|
||||
console.log("subscribeEvents", event);
|
||||
return () => console.log("unsubscribeEvents", event);
|
||||
},
|
||||
},
|
||||
|
||||
// Mock properties
|
||||
mockEntities: entities,
|
||||
|
||||
// Home Assistant functions
|
||||
async callService(domain, service, data) {
|
||||
fireEvent(elements[0], "show-notification", {
|
||||
message: `Called service ${domain}/${service}`,
|
||||
});
|
||||
if (data.entity_id) {
|
||||
await Promise.all(
|
||||
ensureArray(data.entity_id).map((ent) =>
|
||||
entities[ent].handleService(domain, service, data)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log("unmocked callService", domain, service, data);
|
||||
}
|
||||
},
|
||||
|
||||
async callWS(msg) {
|
||||
const callback = wsCommands[msg.type];
|
||||
return callback
|
||||
? callback(msg)
|
||||
: Promise.reject({
|
||||
code: "command_not_mocked",
|
||||
message: "This command is not implemented in the gallery.",
|
||||
});
|
||||
},
|
||||
|
||||
async sendWS(msg) {
|
||||
const callback = wsCommands[msg.type];
|
||||
|
||||
if (callback) {
|
||||
callback(msg);
|
||||
} else {
|
||||
console.error(`Unknown command: ${msg.type}`);
|
||||
}
|
||||
console.log("sendWS", msg);
|
||||
},
|
||||
|
||||
async callApi(method, path, parameters) {
|
||||
const callback = restResponses[path];
|
||||
|
||||
return callback
|
||||
? callback(method, path, parameters)
|
||||
: Promise.reject(`Mock for {path} is not implemented`);
|
||||
},
|
||||
|
||||
// Mock functions
|
||||
updateHass,
|
||||
updateStates(newStates) {
|
||||
updateHass({
|
||||
states: Object.assign({}, hass.states, newStates),
|
||||
});
|
||||
},
|
||||
addEntities(newEntities) {
|
||||
const states = {};
|
||||
ensureArray(newEntities).forEach((ent) => {
|
||||
ent.hass = hass;
|
||||
entities[ent.entityId] = ent;
|
||||
states[ent.entityId] = ent.toState();
|
||||
});
|
||||
this.updateStates(states);
|
||||
},
|
||||
mockWS(type, callback) {
|
||||
wsCommands[type] = callback;
|
||||
},
|
||||
mockAPI(path, callback) {
|
||||
restResponses[path] = callback;
|
||||
},
|
||||
});
|
||||
|
||||
return hass;
|
||||
};
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
|
@ -4,8 +4,8 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-content";
|
||||
import "../../../src/components/ha-card";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
import "../components/demo-more-infos";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
|
||||
|
@ -2,12 +2,13 @@ const path = require("path");
|
||||
const gulp = require("gulp");
|
||||
const foreach = require("gulp-foreach");
|
||||
const hash = require("gulp-hash");
|
||||
const insert = require("gulp-insert");
|
||||
const merge = require("gulp-merge-json");
|
||||
const minify = require("gulp-jsonminify");
|
||||
const rename = require("gulp-rename");
|
||||
const transform = require("gulp-json-transform");
|
||||
|
||||
const isDemo = process.env.DEMO === "1";
|
||||
|
||||
const inDir = "translations";
|
||||
const workDir = "build-translations";
|
||||
const fullDir = workDir + "/full";
|
||||
@ -230,7 +231,7 @@ gulp.task(taskName, ["build-flattened-translations"], function() {
|
||||
hash({
|
||||
algorithm: "md5",
|
||||
hashLength: 32,
|
||||
template: "<%= name %>-<%= hash %>.json",
|
||||
template: isDemo ? "<%= name %>.json" : "<%= name %>-<%= hash %>.json",
|
||||
})
|
||||
)
|
||||
.pipe(hash.manifest("translationFingerprints.json"))
|
||||
|
@ -5,10 +5,18 @@ export const navigate = (
|
||||
path: string,
|
||||
replace: boolean = false
|
||||
) => {
|
||||
if (replace) {
|
||||
history.replaceState(null, "", path);
|
||||
if (__DEMO__) {
|
||||
if (replace) {
|
||||
history.replaceState(null, "", `${location.pathname}#${path}`);
|
||||
} else {
|
||||
window.location.hash = path;
|
||||
}
|
||||
} else {
|
||||
history.pushState(null, "", path);
|
||||
if (replace) {
|
||||
history.replaceState(null, "", path);
|
||||
} else {
|
||||
history.pushState(null, "", path);
|
||||
}
|
||||
}
|
||||
fireEvent(node, "location-changed");
|
||||
};
|
||||
|
19
src/fake_data/demo_config.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { HassConfig } from "home-assistant-js-websocket";
|
||||
|
||||
export const demoConfig: HassConfig = {
|
||||
location_name: "Home",
|
||||
elevation: 300,
|
||||
latitude: 51.5287352,
|
||||
longitude: -0.381773,
|
||||
unit_system: {
|
||||
length: "km",
|
||||
mass: "kg",
|
||||
temperature: "°C",
|
||||
volume: "L",
|
||||
},
|
||||
components: [],
|
||||
time_zone: "America/Los_Angeles",
|
||||
config_dir: "/config",
|
||||
version: "DEMO",
|
||||
whitelist_external_dirs: [],
|
||||
};
|
96
src/fake_data/demo_panels.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { Panels } from "../types";
|
||||
|
||||
export const demoPanels: Panels = {
|
||||
lovelace: {
|
||||
component_name: "lovelace",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: { mode: "storage" },
|
||||
url_path: "lovelace",
|
||||
},
|
||||
"dev-state": {
|
||||
component_name: "dev-state",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "dev-state",
|
||||
},
|
||||
states: {
|
||||
component_name: "states",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "states",
|
||||
},
|
||||
"dev-event": {
|
||||
component_name: "dev-event",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "dev-event",
|
||||
},
|
||||
"dev-template": {
|
||||
component_name: "dev-template",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "dev-template",
|
||||
},
|
||||
profile: {
|
||||
component_name: "profile",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "profile",
|
||||
},
|
||||
kiosk: {
|
||||
component_name: "kiosk",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "kiosk",
|
||||
},
|
||||
"dev-info": {
|
||||
component_name: "dev-info",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "dev-info",
|
||||
},
|
||||
"dev-mqtt": {
|
||||
component_name: "dev-mqtt",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "dev-mqtt",
|
||||
},
|
||||
"dev-service": {
|
||||
component_name: "dev-service",
|
||||
icon: null,
|
||||
title: null,
|
||||
config: null,
|
||||
url_path: "dev-service",
|
||||
},
|
||||
// Uncomment when we are ready to stub the history API
|
||||
// history: {
|
||||
// component_name: "history",
|
||||
// icon: "hass:poll-box",
|
||||
// title: "history",
|
||||
// config: null,
|
||||
// url_path: "history",
|
||||
// },
|
||||
map: {
|
||||
component_name: "map",
|
||||
icon: "hass:account-location",
|
||||
title: "map",
|
||||
config: null,
|
||||
url_path: "map",
|
||||
},
|
||||
config: {
|
||||
component_name: "config",
|
||||
icon: "hass:settings",
|
||||
title: "config",
|
||||
config: null,
|
||||
url_path: "config",
|
||||
},
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
export const demoResources = {
|
||||
en: {
|
||||
"state.default.off": "Off",
|
||||
"state.default.on": "On",
|
1217
src/fake_data/demo_services.ts
Normal file
@ -1,36 +1,53 @@
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntities,
|
||||
} from "home-assistant-js-websocket";
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
const now = () => new Date().toISOString();
|
||||
const randomTime = () =>
|
||||
new Date(new Date().getTime() - Math.random() * 80 * 60 * 1000).toISOString();
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
export class Entity {
|
||||
public domain: string;
|
||||
public objectId: string;
|
||||
public entityId: string;
|
||||
public lastChanged: string;
|
||||
public lastUpdated: string;
|
||||
public state: string;
|
||||
public baseAttributes: HassEntityAttributeBase & { [key: string]: any };
|
||||
public attributes: HassEntityAttributeBase & { [key: string]: any };
|
||||
public hass?: any;
|
||||
|
||||
constructor(domain, objectId, state, baseAttributes) {
|
||||
this.domain = domain;
|
||||
this.objectId = objectId;
|
||||
this.entityId = `${domain}.${objectId}`;
|
||||
this.lastChanged = randomTime();
|
||||
this.lastUpdated = randomTime();
|
||||
this.state = state;
|
||||
this.state = String(state);
|
||||
// These are the attributes that we always write to the state machine
|
||||
this.baseAttributes = baseAttributes;
|
||||
this.attributes = baseAttributes;
|
||||
}
|
||||
|
||||
async handleService(domain, service, data) {
|
||||
public async handleService(domain, service, data: { [key: string]: any }) {
|
||||
// tslint:disable-next-line
|
||||
console.log(
|
||||
`Unmocked service for ${this.entityId}: ${domain}/${service}`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
update(state, attributes = {}) {
|
||||
public update(state, attributes = {}) {
|
||||
this.state = state;
|
||||
this.lastUpdated = now();
|
||||
this.lastChanged =
|
||||
state === this.state ? this.lastChanged : this.lastUpdated;
|
||||
this.attributes = Object.assign({}, this.baseAttributes, attributes);
|
||||
this.attributes = { ...this.baseAttributes, ...attributes };
|
||||
|
||||
// tslint:disable-next-line
|
||||
console.log("update", this.entityId, this);
|
||||
|
||||
this.hass.updateStates({
|
||||
@ -38,7 +55,7 @@ export class Entity {
|
||||
});
|
||||
}
|
||||
|
||||
toState() {
|
||||
public toState() {
|
||||
return {
|
||||
entity_id: this.entityId,
|
||||
state: this.state,
|
||||
@ -50,21 +67,16 @@ export class Entity {
|
||||
}
|
||||
|
||||
export class LightEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (!["homeassistant", this.domain].includes(domain)) return;
|
||||
public async handleService(domain, service, data) {
|
||||
if (!["homeassistant", this.domain].includes(domain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "turn_on") {
|
||||
// eslint-disable-next-line
|
||||
// tslint:disable-next-line
|
||||
let { brightness, hs_color, brightness_pct } = data;
|
||||
// eslint-disable-next-line
|
||||
brightness = (255 * brightness_pct) / 100;
|
||||
this.update(
|
||||
"on",
|
||||
Object.assign(this.attributes, {
|
||||
brightness,
|
||||
hs_color,
|
||||
})
|
||||
);
|
||||
this.update("on", { ...this.attributes, brightness, hs_color });
|
||||
} else if (service === "turn_off") {
|
||||
this.update("off");
|
||||
} else if (service === "toggle") {
|
||||
@ -77,9 +89,36 @@ export class LightEntity extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
export class SwitchEntity extends Entity {
|
||||
public async handleService(domain, service, data) {
|
||||
if (!["homeassistant", this.domain].includes(domain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "turn_on") {
|
||||
this.update("on", this.attributes);
|
||||
} else if (service === "turn_off") {
|
||||
this.update("off", this.attributes);
|
||||
} else if (service === "toggle") {
|
||||
if (this.state === "on") {
|
||||
this.handleService(domain, "turn_off", data);
|
||||
} else {
|
||||
this.handleService(domain, "turn_on", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LockEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) return;
|
||||
public async handleService(
|
||||
domain,
|
||||
service,
|
||||
// @ts-ignore
|
||||
data
|
||||
) {
|
||||
if (domain !== this.domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "lock") {
|
||||
this.update("locked");
|
||||
@ -90,8 +129,15 @@ export class LockEntity extends Entity {
|
||||
}
|
||||
|
||||
export class CoverEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) return;
|
||||
public async handleService(
|
||||
domain,
|
||||
service,
|
||||
// @ts-ignore
|
||||
data
|
||||
) {
|
||||
if (domain !== this.domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "open_cover") {
|
||||
this.update("open");
|
||||
@ -102,23 +148,25 @@ export class CoverEntity extends Entity {
|
||||
}
|
||||
|
||||
export class ClimateEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) return;
|
||||
public async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "set_operation_mode") {
|
||||
this.update(
|
||||
data.operation_mode === "heat" ? "heat" : data.operation_mode,
|
||||
Object.assign(this.attributes, {
|
||||
operation_mode: data.operation_mode,
|
||||
})
|
||||
{ ...this.attributes, operation_mode: data.operation_mode }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (!["homeassistant", this.domain].includes(domain)) return;
|
||||
public async handleService(domain, service, data) {
|
||||
if (!["homeassistant", this.domain].includes(domain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
this.attributes.entity_id.map((ent) => {
|
||||
@ -133,11 +181,24 @@ export class GroupEntity extends Entity {
|
||||
|
||||
const TYPES = {
|
||||
climate: ClimateEntity,
|
||||
light: LightEntity,
|
||||
lock: LockEntity,
|
||||
cover: CoverEntity,
|
||||
group: GroupEntity,
|
||||
light: LightEntity,
|
||||
lock: LockEntity,
|
||||
switch: SwitchEntity,
|
||||
};
|
||||
|
||||
export default (domain, objectId, state, baseAttributes = {}) =>
|
||||
export const getEntity = (
|
||||
domain,
|
||||
objectId,
|
||||
state,
|
||||
baseAttributes = {}
|
||||
): Entity =>
|
||||
new (TYPES[domain] || Entity)(domain, objectId, state, baseAttributes);
|
||||
|
||||
export const convertEntities = (states: HassEntities): Entity[] =>
|
||||
Object.keys(states).map((entId) => {
|
||||
const stateObj = states[entId];
|
||||
const [domain, objectId] = entId.split(".", 2);
|
||||
return getEntity(domain, objectId, stateObj.state, stateObj.attributes);
|
||||
});
|
206
src/fake_data/provide_hass.ts
Normal file
@ -0,0 +1,206 @@
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
import { demoConfig } from "./demo_config";
|
||||
import { demoServices } from "./demo_services";
|
||||
import { demoResources } from "./demo_resources";
|
||||
import { demoPanels } from "./demo_panels";
|
||||
import { getEntity, Entity } from "./entity";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { HassEntities } from "home-assistant-js-websocket";
|
||||
|
||||
const ensureArray = <T>(val: T | T[]): T[] =>
|
||||
Array.isArray(val) ? val : [val];
|
||||
|
||||
export interface MockHomeAssistant extends HomeAssistant {
|
||||
mockEntities: any;
|
||||
updateHass(obj: Partial<MockHomeAssistant>);
|
||||
updateStates(newStates: HassEntities);
|
||||
addEntities(entites: Entity | Entity[], replace?: boolean);
|
||||
mockWS(type: string, callback: (msg: any) => any);
|
||||
mockAPI(
|
||||
path: string,
|
||||
callback: (
|
||||
method: string,
|
||||
path: string,
|
||||
parameters: { [key: string]: any }
|
||||
) => any
|
||||
);
|
||||
}
|
||||
|
||||
export const provideHass = (
|
||||
elements,
|
||||
{ initialStates = {}, panelUrl = "" } = {}
|
||||
): MockHomeAssistant => {
|
||||
elements = ensureArray(elements);
|
||||
|
||||
const wsCommands = {};
|
||||
const restResponses = {};
|
||||
let hass: MockHomeAssistant;
|
||||
const entities = {};
|
||||
|
||||
function updateHass(obj: Partial<MockHomeAssistant>) {
|
||||
hass = { ...hass, ...obj };
|
||||
elements.forEach((el) => {
|
||||
el.hass = hass;
|
||||
});
|
||||
}
|
||||
|
||||
function updateStates(newStates: HassEntities) {
|
||||
updateHass({
|
||||
states: { ...hass.states, ...newStates },
|
||||
});
|
||||
}
|
||||
|
||||
function addEntities(newEntities, replace: boolean = false) {
|
||||
const states = {};
|
||||
ensureArray(newEntities).forEach((ent) => {
|
||||
ent.hass = hass;
|
||||
entities[ent.entityId] = ent;
|
||||
states[ent.entityId] = ent.toState();
|
||||
});
|
||||
if (replace) {
|
||||
updateHass({
|
||||
states,
|
||||
});
|
||||
} else {
|
||||
updateStates(states);
|
||||
}
|
||||
}
|
||||
|
||||
function mockUpdateStateAPI(
|
||||
// @ts-ignore
|
||||
method,
|
||||
path,
|
||||
parameters
|
||||
) {
|
||||
const [domain, objectId] = path.substr(7).split(".", 2);
|
||||
if (!domain || !objectId) {
|
||||
return;
|
||||
}
|
||||
addEntities(
|
||||
getEntity(domain, objectId, parameters.state, parameters.attributes)
|
||||
);
|
||||
}
|
||||
|
||||
updateHass({
|
||||
// Home Assistant properties
|
||||
config: demoConfig,
|
||||
services: demoServices,
|
||||
language: "en",
|
||||
resources: demoResources,
|
||||
states: initialStates,
|
||||
themes: {
|
||||
default_theme: "default",
|
||||
themes: {},
|
||||
},
|
||||
panelUrl: panelUrl || "lovelace",
|
||||
panels: demoPanels,
|
||||
connection: {
|
||||
addEventListener: () => undefined,
|
||||
removeEventListener: () => undefined,
|
||||
sendMessagePromise: () =>
|
||||
new Promise(() => {
|
||||
/* we never resolve */
|
||||
}),
|
||||
subscribeEvents: async (
|
||||
// @ts-ignore
|
||||
callback,
|
||||
event
|
||||
) => {
|
||||
// tslint:disable-next-line
|
||||
console.log("subscribeEvents", event);
|
||||
// tslint:disable-next-line
|
||||
return () => console.log("unsubscribeEvents", event);
|
||||
},
|
||||
socket: {
|
||||
readyState: WebSocket.OPEN,
|
||||
},
|
||||
} as any,
|
||||
translationMetadata: {
|
||||
fragments: [],
|
||||
translations: {},
|
||||
},
|
||||
auth: {} as any,
|
||||
connected: true,
|
||||
dockedSidebar: false,
|
||||
moreInfoEntityId: "",
|
||||
user: {
|
||||
credentials: [],
|
||||
id: "abcd",
|
||||
is_owner: true,
|
||||
mfa_modules: [],
|
||||
name: "Demo User",
|
||||
},
|
||||
fetchWithAuth: () => Promise.reject("Not implemented"),
|
||||
|
||||
// Mock properties
|
||||
mockEntities: entities,
|
||||
|
||||
// Home Assistant functions
|
||||
async callService(domain, service, data) {
|
||||
fireEvent(elements[0], "hass-notification", {
|
||||
message: `Called service ${domain}/${service}`,
|
||||
});
|
||||
if (data && "entity_id" in data) {
|
||||
await Promise.all(
|
||||
ensureArray(data.entity_id).map((ent) =>
|
||||
entities[ent].handleService(domain, service, data)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// tslint:disable-next-line
|
||||
console.log("unmocked callService", domain, service, data);
|
||||
}
|
||||
},
|
||||
|
||||
async callWS(msg) {
|
||||
const callback = wsCommands[msg.type];
|
||||
return callback
|
||||
? callback(msg)
|
||||
: Promise.reject({
|
||||
code: "command_not_mocked",
|
||||
message: `WS Command ${
|
||||
msg.type
|
||||
} is not implemented in provide_hass.`,
|
||||
});
|
||||
},
|
||||
|
||||
async sendWS(msg) {
|
||||
const callback = wsCommands[msg.type];
|
||||
|
||||
if (callback) {
|
||||
callback(msg);
|
||||
} else {
|
||||
// tslint:disable-next-line
|
||||
console.error(`Unknown WS command: ${msg.type}`);
|
||||
}
|
||||
// tslint:disable-next-line
|
||||
console.log("sendWS", msg);
|
||||
},
|
||||
|
||||
async callApi(method, path, parameters) {
|
||||
const callback =
|
||||
path.substr(0, 7) === "states/"
|
||||
? mockUpdateStateAPI
|
||||
: restResponses[path];
|
||||
|
||||
return callback
|
||||
? callback(method, path, parameters)
|
||||
: Promise.reject(`API Mock for ${path} is not implemented`);
|
||||
},
|
||||
|
||||
// Mock functions
|
||||
updateHass,
|
||||
updateStates,
|
||||
addEntities,
|
||||
mockWS(type, callback) {
|
||||
wsCommands[type] = callback;
|
||||
},
|
||||
mockAPI(path, callback) {
|
||||
restResponses[path] = callback;
|
||||
},
|
||||
} as MockHomeAssistant);
|
||||
|
||||
// @ts-ignore
|
||||
return hass;
|
||||
};
|
@ -28,7 +28,7 @@ LitElement.prototype.html = litHtml;
|
||||
const ext = (baseClass, mixins) =>
|
||||
mixins.reduceRight((base, mixin) => mixin(base), baseClass);
|
||||
|
||||
class HomeAssistant extends ext(PolymerElement, [
|
||||
export class HomeAssistant extends ext(PolymerElement, [
|
||||
AuthMixin,
|
||||
ThemesMixin,
|
||||
TranslationsMixin,
|
||||
@ -42,7 +42,10 @@ class HomeAssistant extends ext(PolymerElement, [
|
||||
]) {
|
||||
static get template() {
|
||||
return html`
|
||||
<app-location route="{{route}}"></app-location>
|
||||
<app-location
|
||||
route="{{route}}"
|
||||
use-hash-as-path="[[_useHashAsPath]]"
|
||||
></app-location>
|
||||
<app-route
|
||||
route="{{route}}"
|
||||
pattern="/:panel"
|
||||
@ -102,6 +105,10 @@ class HomeAssistant extends ext(PolymerElement, [
|
||||
);
|
||||
}
|
||||
|
||||
get _useHashAsPath() {
|
||||
return __DEMO__;
|
||||
}
|
||||
|
||||
panelUrlChanged(newPanelUrl) {
|
||||
super.panelUrlChanged(newPanelUrl);
|
||||
this._updateHass({ panelUrl: newPanelUrl });
|
||||
|
@ -135,7 +135,7 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (document.location.pathname === "/") {
|
||||
if (this.route.prefix === "") {
|
||||
this.navigate(`/${localStorage.defaultPage || DEFAULT_PANEL}`, true);
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +217,9 @@ class PartialPanelResolver extends NavigateMixin(PolymerElement) {
|
||||
);
|
||||
this._state = "loaded";
|
||||
},
|
||||
() => {
|
||||
(err) => {
|
||||
// eslint-disable-next-line
|
||||
console.error("Error loading panel", err);
|
||||
this._state = "error";
|
||||
}
|
||||
);
|
||||
|
@ -183,13 +183,18 @@ const generateViewConfig = (
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
const view: LovelaceViewConfig = {
|
||||
path,
|
||||
title,
|
||||
icon,
|
||||
badges,
|
||||
cards,
|
||||
};
|
||||
|
||||
if (icon) {
|
||||
view.icon = icon;
|
||||
}
|
||||
|
||||
return view;
|
||||
};
|
||||
|
||||
export const generateLovelaceConfig = (
|
||||
@ -263,6 +268,12 @@ export const generateLovelaceConfig = (
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEMO__) {
|
||||
views[0].cards!.unshift({
|
||||
type: "custom:ha-demo-card",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
views,
|
||||
|
@ -68,6 +68,24 @@ export class HUIView extends hassLocalizeLitMixin(LitElement) {
|
||||
this._badges = [];
|
||||
}
|
||||
|
||||
// Public to make demo happy
|
||||
public createCardElement(cardConfig: LovelaceCardConfig) {
|
||||
const element = createCardElement(cardConfig) as LovelaceCard;
|
||||
element.hass = this.hass;
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev) => {
|
||||
// In edit mode let it go to hui-root and rebuild whole view.
|
||||
if (!this.lovelace!.editMode) {
|
||||
ev.stopPropagation();
|
||||
this._rebuildCard(element, cardConfig);
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
${this.renderStyles()}
|
||||
@ -240,7 +258,7 @@ export class HUIView extends hassLocalizeLitMixin(LitElement) {
|
||||
const elements: LovelaceCard[] = [];
|
||||
const elementsToAppend: HTMLElement[] = [];
|
||||
config.cards.forEach((cardConfig, cardIndex) => {
|
||||
const element = this._createCardElement(cardConfig);
|
||||
const element = this.createCardElement(cardConfig);
|
||||
elements.push(element);
|
||||
|
||||
if (!this.lovelace!.editMode) {
|
||||
@ -288,28 +306,11 @@ export class HUIView extends hassLocalizeLitMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _createCardElement(cardConfig: LovelaceCardConfig) {
|
||||
const element = createCardElement(cardConfig) as LovelaceCard;
|
||||
element.hass = this.hass;
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev) => {
|
||||
// In edit mode let it go to hui-root and rebuild whole view.
|
||||
if (!this.lovelace!.editMode) {
|
||||
ev.stopPropagation();
|
||||
this._rebuildCard(element, cardConfig);
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
private _rebuildCard(
|
||||
cardElToReplace: LovelaceCard,
|
||||
config: LovelaceCardConfig
|
||||
): void {
|
||||
const newCardEl = this._createCardElement(config);
|
||||
const newCardEl = this.createCardElement(config);
|
||||
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
|
||||
this._cards = this._cards!.map((curCardEl) =>
|
||||
curCardEl === cardElToReplace ? newCardEl : curCardEl
|
||||
|
@ -11,5 +11,8 @@ declare global {
|
||||
entityId: string;
|
||||
};
|
||||
"location-changed": undefined;
|
||||
"hass-notification": {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
|
||||
declare global {
|
||||
var __DEV__: boolean;
|
||||
var __DEMO__: boolean;
|
||||
var __BUILD__: "latest" | "es5";
|
||||
var __VERSION__: string;
|
||||
}
|
||||
@ -53,9 +54,9 @@ export interface Themes {
|
||||
|
||||
export interface Panel {
|
||||
component_name: string;
|
||||
config?: { [key: string]: any };
|
||||
icon: string;
|
||||
title: string;
|
||||
config: { [key: string]: any } | null;
|
||||
icon: string | null;
|
||||
title: string | null;
|
||||
url_path: string;
|
||||
}
|
||||
|
||||
|
@ -71,4 +71,6 @@ class HaUrlSync extends EventsMixin(PolymerElement) {
|
||||
window.removeEventListener("popstate", this.popstateChangeListener);
|
||||
}
|
||||
}
|
||||
customElements.define("ha-url-sync", HaUrlSync);
|
||||
if (!__DEMO__) {
|
||||
customElements.define("ha-url-sync", HaUrlSync);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ const serviceWorkerUrl =
|
||||
__BUILD__ === "latest" ? "/service_worker.js" : "/service_worker_es5";
|
||||
|
||||
export default () => {
|
||||
if (!("serviceWorker" in navigator)) return;
|
||||
if (!("serviceWorker" in navigator) || __DEMO__) return;
|
||||
|
||||
navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
|
||||
reg.addEventListener("updatefound", () => {
|
||||
|
@ -35,7 +35,7 @@ const generateJSPage = (entrypoint, latestBuild) => {
|
||||
};
|
||||
|
||||
function createConfig(isProdBuild, latestBuild) {
|
||||
let buildPath = latestBuild ? "hass_frontend/" : "hass_frontend_es5/";
|
||||
const buildPath = latestBuild ? "hass_frontend/" : "hass_frontend_es5/";
|
||||
const publicPath = latestBuild ? "/frontend_latest/" : "/frontend_es5/";
|
||||
|
||||
const entry = {
|
||||
@ -104,6 +104,7 @@ function createConfig(isProdBuild, latestBuild) {
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: JSON.stringify(!isProdBuild),
|
||||
__DEMO__: false,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify(VERSION),
|
||||
__STATIC_PATH__: "/static/",
|
||||
|
@ -7356,9 +7356,9 @@ hoek@4.x.x:
|
||||
integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
|
||||
|
||||
home-assistant-js-websocket@^3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.2.4.tgz#0c4212e6ac57b60ed939aa420253994e4f9f0bef"
|
||||
integrity sha512-DaHpWIjJFLwTWNbHeGSCEUsbeyLUWAyWUgsYkiVWxzbfm+vqC5YaLNRu+Ma64SQYh5yGSYr7h25p2hip1GvyhQ==
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.2.5.tgz#ac4fa6a7cb5cb48efe2a49390cf24acb5439f51f"
|
||||
integrity sha512-CRlq9WA1WGw9lVzouK4BxEGEP5JqWV2MBBZyiUPVgBLHPR9p3bJL/y+jNhnjqEyb8QNPVboGuAJ+Rylrl7o2dg==
|
||||
|
||||
home-or-tmp@^2.0.0:
|
||||
version "2.0.0"
|
||||
|