mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Tweaks (#1489)
* Tweaks * Lint * No need for copy plugin * Allow handling more complex service calls * Missed a state * Add locks * Lint * Add cover entity * Make generic entity constructor * Light to handle homeassistant.X services * Lint * Fix translations * final tweaks
This commit is contained in:
parent
3b2d4de313
commit
bdf26bbccd
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -48,6 +48,10 @@ class DemoCard extends PolymerElement {
|
|||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
|
hass: {
|
||||||
|
type: Object,
|
||||||
|
observer: '_hassChanged',
|
||||||
|
},
|
||||||
config: {
|
config: {
|
||||||
type: Object,
|
type: Object,
|
||||||
observer: '_configChanged'
|
observer: '_configChanged'
|
||||||
@ -62,17 +66,27 @@ class DemoCard extends PolymerElement {
|
|||||||
card.removeChild(card.lastChild);
|
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]);
|
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);
|
card.appendChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_hassChanged(hass) {
|
||||||
|
const card = this.$.card.lastChild;
|
||||||
|
if (card) card.hass = hass;
|
||||||
|
}
|
||||||
|
|
||||||
_trim(config) {
|
_trim(config) {
|
||||||
return config.trim();
|
return config.trim();
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ class DemoCards extends PolymerElement {
|
|||||||
<demo-card
|
<demo-card
|
||||||
config='[[item]]'
|
config='[[item]]'
|
||||||
show-config='[[showConfig]]'
|
show-config='[[showConfig]]'
|
||||||
|
hass='[[hass]]'
|
||||||
></demo-card>
|
></demo-card>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -45,6 +46,7 @@ class DemoCards extends PolymerElement {
|
|||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
configs: Object,
|
configs: Object,
|
||||||
|
hass: Object,
|
||||||
showConfig: {
|
showConfig: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
|
116
gallery/src/data/entity.js
Normal file
116
gallery/src/data/entity.js
Normal file
@ -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);
|
@ -1,6 +1,6 @@
|
|||||||
export default class FakeHass {
|
export default class FakeHass {
|
||||||
constructor() {
|
constructor(states = {}) {
|
||||||
this.states = {};
|
this.states = states;
|
||||||
this._wsCommands = {};
|
this._wsCommands = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
79
gallery/src/data/provide_hass.js
Normal file
79
gallery/src/data/provide_hass.js
Normal file
@ -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;
|
||||||
|
};
|
@ -1,8 +1,45 @@
|
|||||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||||
import { PolymerElement } from '@polymer/polymer/polymer-element.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';
|
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 = [
|
const CONFIGS = [
|
||||||
{
|
{
|
||||||
heading: 'Basic',
|
heading: 'Basic',
|
||||||
@ -48,7 +85,7 @@ const CONFIGS = [
|
|||||||
`
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: 'With title, cant\'t toggle',
|
heading: 'With title, can\'t toggle',
|
||||||
config: `
|
config: `
|
||||||
- type: entities
|
- type: entities
|
||||||
entities:
|
entities:
|
||||||
@ -80,7 +117,11 @@ const CONFIGS = [
|
|||||||
class DemoEntities extends PolymerElement {
|
class DemoEntities extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
<demo-cards
|
||||||
|
id='demos'
|
||||||
|
hass='[[hass]]'
|
||||||
|
configs="[[_configs]]"
|
||||||
|
></demo-cards>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,9 +130,16 @@ class DemoEntities extends PolymerElement {
|
|||||||
_configs: {
|
_configs: {
|
||||||
type: Object,
|
type: Object,
|
||||||
value: CONFIGS
|
value: CONFIGS
|
||||||
}
|
},
|
||||||
|
hass: Object,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ready() {
|
||||||
|
super.ready();
|
||||||
|
const hass = provideHass(this.$.demos);
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('demo-hui-entities-card', DemoEntities);
|
customElements.define('demo-hui-entities-card', DemoEntities);
|
||||||
|
@ -9,6 +9,8 @@ import '@polymer/paper-icon-button/paper-icon-button.js';
|
|||||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
|
|
||||||
|
import '../../src/managers/notification-manager.js';
|
||||||
|
|
||||||
const demos = require.context('./demos', true, /^(.*\.(js$))[^.]*$/im);
|
const demos = require.context('./demos', true, /^(.*\.(js$))[^.]*$/im);
|
||||||
|
|
||||||
const fixPath = path => path.substr(2, path.length - 5);
|
const fixPath = path => path.substr(2, path.length - 5);
|
||||||
@ -91,6 +93,7 @@ class HaGallery extends PolymerElement {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</app-header-layout>
|
</app-header-layout>
|
||||||
|
<notification-manager id='notifications'></notification-manager>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,8 +114,15 @@ class HaGallery extends PolymerElement {
|
|||||||
ready() {
|
ready() {
|
||||||
super.ready();
|
super.ready();
|
||||||
|
|
||||||
|
this.addEventListener(
|
||||||
|
'show-notification',
|
||||||
|
ev => this.$.notifications.showNotification(ev.detail.message)
|
||||||
|
);
|
||||||
|
|
||||||
this.addEventListener('hass-more-info', (ev) => {
|
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); });
|
window.addEventListener('hashchange', () => { this._demo = document.location.hash.substr(1); });
|
||||||
|
@ -49,12 +49,11 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin([
|
||||||
'public',
|
'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/leaflet/dist/leaflet.css', to: 'static/images/leaflet/' },
|
||||||
{ from: '../node_modules/@polymer/font-roboto-local/fonts', to: 'static/fonts' },
|
{ from: '../node_modules/@polymer/font-roboto-local/fonts', to: 'static/fonts' },
|
||||||
{ from: '../node_modules/leaflet/dist/images', to: 'static/images/leaflet/' },
|
{ 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({
|
isProd && new UglifyJsPlugin({
|
||||||
extractComments: true,
|
extractComments: true,
|
||||||
|
@ -13,7 +13,7 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<paper-toast id="toast" text="[[_text]]" no-cancel-on-outside-click="[[_cancelOnOutsideClick]]"></paper-toast>
|
<paper-toast id="toast" no-cancel-on-outside-click="[[_cancelOnOutsideClick]]"></paper-toast>
|
||||||
<paper-toast id="connToast" duration="0" text="[[localize('ui.notification_toast.connection_lost')]]" opened="[[connectionLost]]"></paper-toast>
|
<paper-toast id="connToast" duration="0" text="[[localize('ui.notification_toast.connection_lost')]]" opened="[[connectionLost]]"></paper-toast>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -40,11 +40,6 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
|
|||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
_text: {
|
|
||||||
type: String,
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
toastClass: {
|
toastClass: {
|
||||||
type: String,
|
type: String,
|
||||||
value: '',
|
value: '',
|
||||||
@ -90,8 +85,7 @@ class NotificationManager extends LocalizeMixin(PolymerElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showNotification(message) {
|
showNotification(message) {
|
||||||
this._set_text(message);
|
this.$.toast.show(message);
|
||||||
this.$.toast.show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user