diff --git a/gallery/src/data/media_player_images/media_player.bedroom.jpg b/gallery/public/api/media_player_proxy/media_player.bedroom
similarity index 100%
rename from gallery/src/data/media_player_images/media_player.bedroom.jpg
rename to gallery/public/api/media_player_proxy/media_player.bedroom
diff --git a/gallery/src/data/media_player_images/media_player.living_room.jpg b/gallery/public/api/media_player_proxy/media_player.living_room
similarity index 100%
rename from gallery/src/data/media_player_images/media_player.living_room.jpg
rename to gallery/public/api/media_player_proxy/media_player.living_room
diff --git a/gallery/src/data/media_player_images/media_player.walkman.jpg b/gallery/public/api/media_player_proxy/media_player.walkman
similarity index 100%
rename from gallery/src/data/media_player_images/media_player.walkman.jpg
rename to gallery/public/api/media_player_proxy/media_player.walkman
diff --git a/gallery/src/components/demo-card.js b/gallery/src/components/demo-card.js
index 186584908f..83b0c51f4d 100644
--- a/gallery/src/components/demo-card.js
+++ b/gallery/src/components/demo-card.js
@@ -48,6 +48,10 @@ class DemoCard extends PolymerElement {
static get properties() {
return {
+ hass: {
+ type: Object,
+ observer: '_hassChanged',
+ },
config: {
type: Object,
observer: '_configChanged'
@@ -62,17 +66,27 @@ class DemoCard extends PolymerElement {
card.removeChild(card.lastChild);
}
- const hass = new HomeAssistant();
- hass.config = demoConfig;
- hass.resources = demoResources;
- hass.language = 'en';
- hass.states = demoStates;
-
const el = createCardElement(JsYaml.safeLoad(config.config)[0]);
- el.hass = hass;
+
+ if (this.hass) {
+ el.hass = this.hass;
+ } else {
+ const hass = new HomeAssistant(demoStates);
+ hass.config = demoConfig;
+ hass.resources = demoResources;
+ hass.language = 'en';
+ hass.states = demoStates;
+ el.hass = hass;
+ }
+
card.appendChild(el);
}
+ _hassChanged(hass) {
+ const card = this.$.card.lastChild;
+ if (card) card.hass = hass;
+ }
+
_trim(config) {
return config.trim();
}
diff --git a/gallery/src/components/demo-cards.js b/gallery/src/components/demo-cards.js
index e71c2cf8e2..f4d6edf755 100644
--- a/gallery/src/components/demo-cards.js
+++ b/gallery/src/components/demo-cards.js
@@ -36,6 +36,7 @@ class DemoCards extends PolymerElement {
@@ -45,6 +46,7 @@ class DemoCards extends PolymerElement {
static get properties() {
return {
configs: Object,
+ hass: Object,
showConfig: {
type: Boolean,
value: false,
diff --git a/gallery/src/data/entity.js b/gallery/src/data/entity.js
new file mode 100644
index 0000000000..756354e0fa
--- /dev/null
+++ b/gallery/src/data/entity.js
@@ -0,0 +1,116 @@
+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 {
+ constructor(domain, objectId, state, baseAttributes) {
+ this.domain = domain;
+ this.objectId = objectId;
+ this.entityId = `${domain}.${objectId}`;
+ this.lastChanged = randomTime();
+ this.lastUpdated = randomTime();
+ this.state = state;
+ // These are the attributes that we always write to the state machine
+ this.baseAttributes = baseAttributes;
+ this.attributes = baseAttributes;
+ }
+
+ async handleService(domain, service, data) {
+ console.log(`Unmocked service for ${this.entityId}: ${domain}/${service}`, data);
+ }
+
+ 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);
+
+ console.log('update', this.entityId, this);
+
+ this.hass.updateStates({
+ [this.entityId]: this.toState()
+ });
+ }
+
+ toState() {
+ return {
+ entity_id: this.entityId,
+ state: this.state,
+ attributes: this.attributes,
+ last_changed: this.lastChanged,
+ last_updated: this.lastUpdated,
+ };
+ }
+}
+
+export class LightEntity extends Entity {
+ async handleService(domain, service, data) {
+ if (!['homeassistant', this.domain].includes(domain)) return;
+
+ if (service === 'turn_on') {
+ // eslint-disable-next-line
+ const { brightness, hs_color } = data;
+ this.update('on', Object.assign(this.attributes, {
+ brightness,
+ hs_color,
+ }));
+ } else if (service === 'turn_off') {
+ this.update('off');
+ } 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;
+
+ if (service === 'lock') {
+ this.update('locked');
+ } else if (service === 'unlock') {
+ this.update('unlocked');
+ }
+ }
+}
+
+export class CoverEntity extends Entity {
+ async handleService(domain, service, data) {
+ if (domain !== this.domain) return;
+
+ if (service === 'open_cover') {
+ this.update('open');
+ } else if (service === 'close_cover') {
+ this.update('closing');
+ }
+ }
+}
+
+export class GroupEntity extends Entity {
+ async handleService(domain, service, data) {
+ if (!['homeassistant', this.domain].includes(domain)) return;
+
+ await Promise.all(this.attributes.entity_id.map((ent) => {
+ const entity = this.hass.mockEntities[ent];
+ return entity.handleService(entity.domain, service, data);
+ }));
+
+ this.update(service === 'turn_on' ? 'on' : 'off');
+ }
+}
+
+const TYPES = {
+ light: LightEntity,
+ lock: LockEntity,
+ cover: CoverEntity,
+ group: GroupEntity,
+};
+
+export default (domain, objectId, state, baseAttributes = {}) =>
+ new (TYPES[domain] || Entity)(domain, objectId, state, baseAttributes);
diff --git a/gallery/src/data/hass.js b/gallery/src/data/hass.js
index 59179d6637..918132af4c 100644
--- a/gallery/src/data/hass.js
+++ b/gallery/src/data/hass.js
@@ -1,6 +1,6 @@
export default class FakeHass {
- constructor() {
- this.states = {};
+ constructor(states = {}) {
+ this.states = states;
this._wsCommands = {};
}
diff --git a/gallery/src/data/provide_hass.js b/gallery/src/data/provide_hass.js
new file mode 100644
index 0000000000..8ebadb022b
--- /dev/null
+++ b/gallery/src/data/provide_hass.js
@@ -0,0 +1,79 @@
+import fireEvent from '../../../src/common/dom/fire_event.js';
+
+import demoConfig from './demo_config.js';
+import demoResources from './demo_resources.js';
+
+const ensureArray = val => (Array.isArray(val) ? val : [val]);
+
+export default (elements, { initialStates = {} } = {}) => {
+ elements = ensureArray(elements);
+
+ const wsCommands = {};
+ let hass;
+ const entities = {};
+
+ function updateHass(obj) {
+ hass = Object.assign({}, hass, obj);
+ elements.forEach((el) => { el.hass = hass; });
+ }
+
+ updateHass({
+ // Home Assistant properties
+ config: demoConfig,
+ language: 'en',
+ resources: demoResources,
+ states: initialStates,
+
+ // 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);
+ },
+
+ // 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);
+ }
+ });
+
+ return hass;
+};
diff --git a/gallery/src/demos/demo-hui-entities-card.js b/gallery/src/demos/demo-hui-entities-card.js
index bd4d73cdc1..f473b01bb9 100644
--- a/gallery/src/demos/demo-hui-entities-card.js
+++ b/gallery/src/demos/demo-hui-entities-card.js
@@ -1,8 +1,45 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+import getEntity from '../data/entity.js';
+import provideHass from '../data/provide_hass.js';
import '../components/demo-cards.js';
+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'
+ }),
+];
+
const CONFIGS = [
{
heading: 'Basic',
@@ -48,7 +85,7 @@ const CONFIGS = [
`
},
{
- heading: 'With title, cant\'t toggle',
+ heading: 'With title, can\'t toggle',
config: `
- type: entities
entities:
@@ -80,7 +117,11 @@ const CONFIGS = [
class DemoEntities extends PolymerElement {
static get template() {
return html`
-
+
`;
}
@@ -89,9 +130,16 @@ class DemoEntities extends PolymerElement {
_configs: {
type: Object,
value: CONFIGS
- }
+ },
+ hass: Object,
};
}
+
+ ready() {
+ super.ready();
+ const hass = provideHass(this.$.demos);
+ hass.addEntities(ENTITIES);
+ }
}
customElements.define('demo-hui-entities-card', DemoEntities);
diff --git a/gallery/src/ha-gallery.js b/gallery/src/ha-gallery.js
index fbcd9fc943..d09dd5e0d7 100644
--- a/gallery/src/ha-gallery.js
+++ b/gallery/src/ha-gallery.js
@@ -9,6 +9,8 @@ import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+import '../../src/managers/notification-manager.js';
+
const demos = require.context('./demos', true, /^(.*\.(js$))[^.]*$/im);
const fixPath = path => path.substr(2, path.length - 5);
@@ -91,6 +93,7 @@ class HaGallery extends PolymerElement {
+
`;
}
@@ -111,8 +114,15 @@ class HaGallery extends PolymerElement {
ready() {
super.ready();
+ this.addEventListener(
+ 'show-notification',
+ ev => this.$.notifications.showNotification(ev.detail.message)
+ );
+
this.addEventListener('hass-more-info', (ev) => {
- if (ev.detail.entityId) alert(`Showing more info for ${ev.detail.entityId}`);
+ if (ev.detail.entityId) {
+ this.$.notifications.showNotification(`Showing more info for ${ev.detail.entityId}`);
+ }
});
window.addEventListener('hashchange', () => { this._demo = document.location.hash.substr(1); });
diff --git a/gallery/webpack.config.js b/gallery/webpack.config.js
index 91f782c155..31ced8cfcb 100644
--- a/gallery/webpack.config.js
+++ b/gallery/webpack.config.js
@@ -49,12 +49,11 @@ module.exports = {
plugins: [
new CopyWebpackPlugin([
'public',
+ { 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/' },
- { from: './src/data/media_player_images/media_player.bedroom.jpg', to: 'api/media_player_proxy/media_player.bedroom' },
- { from: './src/data/media_player_images/media_player.living_room.jpg', to: 'api/media_player_proxy/media_player.living_room' },
- { from: './src/data/media_player_images/media_player.walkman.jpg', to: 'api/media_player_proxy/media_player.walkman' },
]),
isProd && new UglifyJsPlugin({
extractComments: true,
diff --git a/src/managers/notification-manager.js b/src/managers/notification-manager.js
index c55eff9347..a3820862df 100644
--- a/src/managers/notification-manager.js
+++ b/src/managers/notification-manager.js
@@ -13,7 +13,7 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
}
-
+
`;
}
@@ -40,11 +40,6 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
value: false,
},
- _text: {
- type: String,
- readOnly: true,
- },
-
toastClass: {
type: String,
value: '',
@@ -90,8 +85,7 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
}
showNotification(message) {
- this._set_text(message);
- this.$.toast.show();
+ this.$.toast.show(message);
}
}