purifier-card 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,33 @@
1
- import { LitElement, html, nothing } from 'lit';
2
- import { hasConfigOrEntityChanged, fireEvent } from 'custom-card-helpers';
1
+ import { CSSResultGroup, LitElement, PropertyValues, html, nothing } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+ import {
4
+ hasConfigOrEntityChanged,
5
+ fireEvent,
6
+ HomeAssistant,
7
+ ServiceCallRequest,
8
+ } from 'custom-card-helpers';
3
9
  import registerTemplates from 'ha-template';
10
+ import get from 'lodash/get';
4
11
  import localize from './localize';
5
12
  import styles from './styles.css';
6
- import { version } from '../package.json';
7
13
  import workingImg from './images/purifier-working.gif';
8
14
  import standbyImg from './images/purifier-standby.png';
9
- import './purifier-card-editor';
15
+
16
+ import {
17
+ PurifierCardConfig,
18
+ PurifierEntity,
19
+ SliderValue,
20
+ Template,
21
+ } from './types';
22
+ import buildConfig from './config';
10
23
 
11
24
  registerTemplates();
12
25
 
26
+ // String in the right side will be replaced by Rollup
27
+ const PKG_VERSION = 'PKG_VERSION_VALUE';
28
+
13
29
  console.info(
14
- `%c PURIFIER-CARD %c ${version} `,
30
+ `%c PURIFIER-CARD %c ${PKG_VERSION} `,
15
31
  'color: white; background: blue; font-weight: 700;',
16
32
  'color: blue; background: white; font-weight: 700;'
17
33
  );
@@ -19,112 +35,55 @@ console.info(
19
35
  if (!customElements.get('ha-icon-button')) {
20
36
  customElements.define(
21
37
  'ha-icon-button',
22
- class extends customElements.get('paper-icon-button') {}
38
+ class extends (customElements.get('paper-icon-button') ?? HTMLElement) {}
23
39
  );
24
40
  }
25
41
 
26
42
  const SUPPORT_PRESET_MODE = 8;
27
- class PurifierCard extends LitElement {
28
- static get properties() {
29
- return {
30
- hass: Object,
31
- config: Object,
32
- requestInProgress: Boolean,
33
- };
34
- }
43
+ @customElement('purifier-card')
44
+ export class PurifierCard extends LitElement {
45
+ @property({ attribute: false }) public hass!: HomeAssistant;
35
46
 
36
- static get styles() {
47
+ @state() private config!: PurifierCardConfig;
48
+ @state() private requestInProgress = false;
49
+
50
+ public static get styles(): CSSResultGroup {
37
51
  return styles;
38
52
  }
39
53
 
40
- static async getConfigElement() {
54
+ public static async getConfigElement() {
55
+ import('./editor');
41
56
  return document.createElement('purifier-card-editor');
42
57
  }
43
58
 
44
- static getStubConfig(hass, entities) {
45
- const [purifierEntity] = entities.filter(
46
- (eid) => eid.substr(0, eid.indexOf('.')) === 'fan'
47
- );
59
+ public static getStubConfig(
60
+ _: unknown,
61
+ entities: string[]
62
+ ): Partial<PurifierCardConfig> {
63
+ const [purifierEntity] = entities.filter((eid) => eid.startsWith('fan'));
48
64
 
49
65
  return {
50
- entity: purifierEntity || '',
66
+ entity: purifierEntity ?? '',
51
67
  };
52
68
  }
53
69
 
54
- get platform() {
55
- if (this.config.platform === undefined) {
56
- return 'xiaomi_miio_airpurifier';
57
- }
58
-
59
- return this.config.platform;
70
+ public setConfig(config: Partial<PurifierCardConfig>) {
71
+ this.config = buildConfig(config);
60
72
  }
61
73
 
62
- get entity() {
63
- return this.hass.states[this.config.entity];
64
- }
65
-
66
- get showPresetMode() {
67
- if (this.config.show_preset_mode === undefined) {
68
- return true;
69
- }
70
-
71
- return this.config.show_preset_mode;
72
- }
73
-
74
- get showName() {
75
- if (this.config.show_name === undefined) {
76
- return true;
77
- }
78
-
79
- return this.config.show_name;
74
+ get entity(): PurifierEntity {
75
+ return this.hass.states[this.config.entity] as PurifierEntity;
80
76
  }
81
77
 
82
- get showState() {
83
- if (this.config.show_state === undefined) {
84
- return true;
85
- }
86
-
87
- return this.config.show_state;
88
- }
89
-
90
- get showToolbar() {
91
- if (this.config.show_toolbar === undefined) {
92
- return true;
93
- }
94
-
95
- return this.config.show_toolbar;
96
- }
97
-
98
- get compactView() {
99
- if (this.config.compact_view === undefined) {
100
- return false;
101
- }
102
-
103
- return this.config.compact_view;
104
- }
105
-
106
- setConfig(config) {
107
- if (!config.entity) {
108
- throw new Error(localize('error.missing_entity'));
109
- }
110
-
111
- const actions = config.actions;
112
- if (actions && Array.isArray(actions)) {
113
- console.warn(localize('warning.actions_array'));
114
- }
115
-
116
- this.config = config;
117
- }
118
-
119
- getCardSize() {
78
+ public getCardSize() {
120
79
  return 2;
121
80
  }
122
81
 
123
- shouldUpdate(changedProps) {
124
- return hasConfigOrEntityChanged(this, changedProps);
82
+ protected shouldUpdate(changedProps: PropertyValues) {
83
+ return hasConfigOrEntityChanged(this, changedProps, false);
125
84
  }
126
85
 
127
- updated(changedProps) {
86
+ protected updated(changedProps: PropertyValues) {
128
87
  if (
129
88
  changedProps.get('hass') &&
130
89
  changedProps.get('hass').states[this.config.entity] !==
@@ -134,7 +93,7 @@ class PurifierCard extends LitElement {
134
93
  }
135
94
  }
136
95
 
137
- handleMore(entityId = this.entity.entity_id) {
96
+ private handleMore(entityId: string = this.entity.entity_id) {
138
97
  fireEvent(
139
98
  this,
140
99
  'hass-more-info',
@@ -148,36 +107,41 @@ class PurifierCard extends LitElement {
148
107
  );
149
108
  }
150
109
 
151
- handlePresetMode(e) {
152
- const preset_mode = e.target.getAttribute('value');
153
- this.callService('fan.set_preset_mode', { preset_mode });
154
- }
155
-
156
- handlePercentage(e) {
157
- const percentage = e.detail.value;
158
- this.callService('fan.set_percentage', { percentage });
159
- }
160
-
161
- callService(service, options = {}, isRequest = true) {
110
+ private callService(
111
+ service: ServiceCallRequest['service'],
112
+ options: ServiceCallRequest['serviceData'] = {},
113
+ request = true
114
+ ) {
162
115
  const [domain, name] = service.split('.');
163
116
  this.hass.callService(domain, name, {
164
117
  entity_id: this.config.entity,
165
118
  ...options,
166
119
  });
167
120
 
168
- if (isRequest) {
121
+ if (request) {
169
122
  this.requestInProgress = true;
170
123
  this.requestUpdate();
171
124
  }
172
125
  }
173
126
 
174
- renderPresetMode() {
127
+ private handlePresetMode(event: PointerEvent) {
128
+ const preset_mode = (<HTMLDivElement>event.target).getAttribute('value');
129
+ this.callService('fan.set_preset_mode', { preset_mode });
130
+ }
131
+
132
+ private handlePercentage(event: CustomEvent<SliderValue>) {
133
+ const percentage = event.detail.value;
134
+ this.callService('fan.set_percentage', { percentage });
135
+ }
136
+
137
+ private renderPresetMode(): Template {
175
138
  const {
176
- attributes: { preset_mode, preset_modes, supported_features },
139
+ attributes: { preset_mode, preset_modes, supported_features = 0 },
177
140
  } = this.entity;
178
141
 
179
142
  if (
180
- !this.showPresetMode ||
143
+ !preset_mode ||
144
+ !this.config.show_preset_mode ||
181
145
  !preset_modes ||
182
146
  !(supported_features & SUPPORT_PRESET_MODE)
183
147
  ) {
@@ -188,7 +152,7 @@ class PurifierCard extends LitElement {
188
152
 
189
153
  return html`
190
154
  <div class="preset-mode">
191
- <ha-button-menu @click="${(e) => e.stopPropagation()}">
155
+ <ha-button-menu @click="${(e: PointerEvent) => e.stopPropagation()}">
192
156
  <mmp-icon-button slot="trigger">
193
157
  <ha-icon icon="mdi:fan"></ha-icon>
194
158
  <span>
@@ -202,9 +166,9 @@ class PurifierCard extends LitElement {
202
166
  <mwc-list-item
203
167
  ?activated=${selected === index}
204
168
  value=${item}
205
- @click=${(e) => this.handlePresetMode(e)}
169
+ @click=${(e: PointerEvent) => this.handlePresetMode(e)}
206
170
  >
207
- ${localize(`preset_mode.${item}`) || item}
171
+ ${localize(`preset_mode.${item.toLowerCase()}`) || item}
208
172
  </mwc-list-item>
209
173
  `
210
174
  )}
@@ -213,19 +177,28 @@ class PurifierCard extends LitElement {
213
177
  `;
214
178
  }
215
179
 
216
- renderAQI() {
180
+ private renderAQI(): Template {
217
181
  const { aqi = {} } = this.config;
218
- const { entity_id, attribute = 'aqi', unit = 'AQI' } = aqi;
182
+ const { entity_id, attribute, unit = 'AQI' } = aqi;
219
183
 
220
- const value = entity_id
221
- ? this.hass.states[entity_id].state
222
- : this.entity.attributes[attribute];
184
+ let value = '';
185
+
186
+ if (entity_id && attribute) {
187
+ value = get(this.hass.states[entity_id].attributes, attribute);
188
+ } else if (attribute) {
189
+ value = get(this.entity.attributes, attribute);
190
+ } else if (entity_id) {
191
+ value = this.hass.states[entity_id].state;
192
+ } else {
193
+ return nothing;
194
+ }
223
195
 
224
- let prefix = '';
196
+ let prefix: Template = nothing;
197
+ const numericValue = Number(value);
225
198
 
226
- if (value < 10) {
199
+ if (numericValue < 10) {
227
200
  prefix = html`<span class="number-off">00</span>`;
228
- } else if (value < 100) {
201
+ } else if (numericValue < 100) {
229
202
  prefix = html`<span class="number-off">0</span>`;
230
203
  }
231
204
 
@@ -237,7 +210,7 @@ class PurifierCard extends LitElement {
237
210
  `;
238
211
  }
239
212
 
240
- renderSlider() {
213
+ private renderSlider(): Template {
241
214
  const {
242
215
  state,
243
216
  attributes: { percentage, percentage_step },
@@ -252,7 +225,8 @@ class PurifierCard extends LitElement {
252
225
  value=${percentage}
253
226
  step=${percentage_step}
254
227
  ?disabled="${disabled}"
255
- @value-changed=${(e) => this.handlePercentage(e)}
228
+ @value-changed=${(e: CustomEvent<SliderValue>) =>
229
+ this.handlePercentage(e)}
256
230
  >
257
231
  </round-slider>
258
232
  <img src=${image} alt="purifier is ${state}" class="image" />
@@ -266,27 +240,27 @@ class PurifierCard extends LitElement {
266
240
  `;
267
241
  }
268
242
 
269
- renderControls() {
270
- return this.compactView ? this.renderAQI() : this.renderSlider();
243
+ private renderControls(): Template {
244
+ return this.config.compact_view ? this.renderAQI() : this.renderSlider();
271
245
  }
272
246
 
273
- renderName() {
247
+ private renderName(): Template {
274
248
  const {
275
249
  attributes: { friendly_name },
276
250
  } = this.entity;
277
251
 
278
- if (!this.showName) {
252
+ if (!this.config.show_name) {
279
253
  return nothing;
280
254
  }
281
255
 
282
256
  return html` <div class="friendly-name">${friendly_name}</div> `;
283
257
  }
284
258
 
285
- renderState() {
259
+ private renderState(): Template {
286
260
  const { state } = this.entity;
287
261
  const localizedState = localize(`state.${state}`) || state;
288
262
 
289
- if (!this.showState) {
263
+ if (!this.config.show_state) {
290
264
  return nothing;
291
265
  }
292
266
 
@@ -303,20 +277,26 @@ class PurifierCard extends LitElement {
303
277
  `;
304
278
  }
305
279
 
306
- renderStats() {
307
- const { stats = [] } = this.config;
308
-
309
- const statsList = stats || [];
280
+ private renderStats(): Template {
281
+ const statsList = this.config.stats || [];
310
282
 
311
- return statsList.map(
283
+ const stats = statsList.map(
312
284
  ({ entity_id, attribute, value_template, unit, subtitle }) => {
313
- if (!entity_id && !attribute && !value_template) {
285
+ if (!entity_id && !attribute) {
314
286
  return nothing;
315
287
  }
316
288
 
317
- const state = entity_id
318
- ? this.hass.states[entity_id].state
319
- : this.entity.attributes[attribute];
289
+ let state = '';
290
+
291
+ if (entity_id && attribute) {
292
+ state = get(this.hass.states[entity_id].attributes, attribute);
293
+ } else if (attribute) {
294
+ state = get(this.entity.attributes, attribute);
295
+ } else if (entity_id) {
296
+ state = this.hass.states[entity_id].state;
297
+ } else {
298
+ return nothing;
299
+ }
320
300
 
321
301
  const value = html`
322
302
  <ha-template
@@ -336,13 +316,15 @@ class PurifierCard extends LitElement {
336
316
  `;
337
317
  }
338
318
  );
319
+
320
+ return stats.length ? html`<div class="stats">${stats}</div>` : nothing;
339
321
  }
340
322
 
341
- renderToolbar() {
323
+ private renderToolbar(): Template {
342
324
  const { shortcuts = [] } = this.config;
343
325
  const { state, attributes } = this.entity;
344
326
 
345
- if (!this.showToolbar) {
327
+ if (!this.config.show_toolbar) {
346
328
  return nothing;
347
329
  }
348
330
 
@@ -398,19 +380,23 @@ class PurifierCard extends LitElement {
398
380
  `;
399
381
  }
400
382
 
401
- render() {
383
+ private renderUnavailable(): Template {
384
+ return html`
385
+ <ha-card>
386
+ <div class="preview not-available">
387
+ <div class="metadata">
388
+ <div class="not-available">
389
+ ${localize('common.not_available')}
390
+ </div>
391
+ <div>
392
+ </div>
393
+ </ha-card>
394
+ `;
395
+ }
396
+
397
+ protected render() {
402
398
  if (!this.entity) {
403
- return html`
404
- <ha-card>
405
- <div class="preview not-available">
406
- <div class="metadata">
407
- <div class="not-available">
408
- ${localize('common.not_available')}
409
- </div>
410
- <div>
411
- </div>
412
- </ha-card>
413
- `;
399
+ return this.renderUnavailable();
414
400
  }
415
401
 
416
402
  return html`
@@ -431,7 +417,7 @@ class PurifierCard extends LitElement {
431
417
 
432
418
  <div class="metadata">${this.renderName()} ${this.renderState()}</div>
433
419
 
434
- <div class="stats">${this.renderStats()}</div>
420
+ ${this.renderStats()}
435
421
  </div>
436
422
 
437
423
  ${this.renderToolbar()}
@@ -440,7 +426,11 @@ class PurifierCard extends LitElement {
440
426
  }
441
427
  }
442
428
 
443
- customElements.define('purifier-card', PurifierCard);
429
+ declare global {
430
+ interface Window {
431
+ customCards?: unknown[];
432
+ }
433
+ }
444
434
 
445
435
  window.customCards = window.customCards || [];
446
436
  window.customCards.push({
package/src/styles.css CHANGED
@@ -23,6 +23,7 @@ ha-card {
23
23
  flex: 1;
24
24
  position: relative;
25
25
  padding: 0px;
26
+ overflow: hidden;
26
27
  }
27
28
 
28
29
  .fill-gap {
@@ -189,7 +190,7 @@ ha-card {
189
190
 
190
191
  .stats-block {
191
192
  cursor: pointer;
192
- margin: var(--pc-spacing) 0px;
193
+ padding: var(--pc-spacing) 0px;
193
194
  text-align: center;
194
195
  border-right: 1px solid var(--pc-divider-color);
195
196
  flex-grow: 1;
@@ -10,10 +10,10 @@
10
10
  "off": "Изключен"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Автоматичен режим",
14
- "Silent": "Тих режим",
15
- "Favorite": "Любима",
16
- "Fan": "Вентилатор"
13
+ "auto": "Автоматичен режим",
14
+ "silent": "Тих режим",
15
+ "favorite": "Любима",
16
+ "fan": "Вентилатор"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Избирането на обект е задължително!"
@@ -10,10 +10,10 @@
10
10
  "off": "Apagat"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Auto",
14
- "Silent": "Silenciós",
15
- "Favorite": "Favorit",
16
- "Fan": "Ventilador"
13
+ "auto": "Auto",
14
+ "silent": "Silenciós",
15
+ "favorite": "Favorit",
16
+ "fan": "Ventilador"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Cal especificar una entitat."
@@ -10,17 +10,14 @@
10
10
  "off": "Vypnuto"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Automatický",
14
- "Silent": "Noční",
15
- "Favorite": "Manuální",
16
- "Fan": "Větrák"
13
+ "auto": "Automatický",
14
+ "silent": "Noční",
15
+ "favorite": "Manuální",
16
+ "fan": "Větrák"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Je vyžadováno specifikování entity!"
20
20
  },
21
- "warning": {
22
- "actions_array": "VAROVÁNÍ: 'actions' je přejmenováno na 'shortcuts'"
23
- },
24
21
  "editor": {
25
22
  "entity": "Entity (povinné)",
26
23
  "compact_view": "Kompaktní zobrazení",
@@ -10,10 +10,10 @@
10
10
  "off": "Aus"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Auto",
14
- "Silent": "Leise",
15
- "Favorite": "Favorit",
16
- "Fan": "Lüfter"
13
+ "auto": "Auto",
14
+ "silent": "Leise",
15
+ "favorite": "Favorit",
16
+ "fan": "Lüfter"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Es muss eine Entity definiert werden!"
@@ -10,17 +10,14 @@
10
10
  "off": "Off"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Auto",
14
- "Silent": "Silent",
15
- "Favorite": "Favorite",
16
- "Fan": "Fan"
13
+ "auto": "Auto",
14
+ "silent": "Silent",
15
+ "favorite": "Favorite",
16
+ "fan": "Fan"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Specifying entity is required!"
20
20
  },
21
- "warning": {
22
- "actions_array": "WARNING: 'actions' is renamed to 'shortcuts'"
23
- },
24
21
  "editor": {
25
22
  "entity": "Entity (Required)",
26
23
  "compact_view": "Compact View",
@@ -10,10 +10,10 @@
10
10
  "off": "Éteint"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Auto",
14
- "Silent": "Nuit",
15
- "Favorite": "Favori",
16
- "Fan": "Manuel"
13
+ "auto": "Auto",
14
+ "silent": "Nuit",
15
+ "favorite": "Favori",
16
+ "fan": "Manuel"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Il est obligatoire de spécifier une entité!"
@@ -10,10 +10,10 @@
10
10
  "off": "Spento"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Auto",
14
- "Silent": "Silenzioso",
15
- "Favorite": "Preferito",
16
- "Fan": "Ventola"
13
+ "auto": "Auto",
14
+ "silent": "Silenzioso",
15
+ "favorite": "Preferito",
16
+ "fan": "Ventola"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "É necessario specificare l'entità"
@@ -10,10 +10,10 @@
10
10
  "off": "Av"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Auto",
14
- "Silent": "Stille",
15
- "Favorite": "Favoritt",
16
- "Fan": "Vifte"
13
+ "auto": "Auto",
14
+ "silent": "Stille",
15
+ "favorite": "Favoritt",
16
+ "fan": "Vifte"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Spesifiserende enhet kreves!"
@@ -10,17 +10,14 @@
10
10
  "off": "Uit"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Auto",
14
- "Silent": "Stil",
15
- "Favorite": "Favoriet",
16
- "Fan": "Ventilator"
13
+ "auto": "Auto",
14
+ "silent": "Stil",
15
+ "favorite": "Favoriet",
16
+ "fan": "Ventilator"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Instellen van entity is verplicht!"
20
20
  },
21
- "warning": {
22
- "actions_array": "WARNING: 'actions' is hernoemd naar 'shortcuts'"
23
- },
24
21
  "editor": {
25
22
  "entity": "Entity (Verplicht)",
26
23
  "compact_view": "Compact View",
@@ -10,11 +10,11 @@
10
10
  "off": "Wyłączony"
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Auto",
14
- "Silent": "Cichy",
15
- "Favorite": "Ulubiony",
16
- "Fan": "Wentylator",
17
- "Idle": "Bezczynny"
13
+ "auto": "Auto",
14
+ "silent": "Cichy",
15
+ "favorite": "Ulubiony",
16
+ "fan": "Wentylator",
17
+ "idle": "Bezczynny"
18
18
  },
19
19
  "error": {
20
20
  "missing_entity": "Wymagane jest zadeklarowanie encji!"
@@ -10,10 +10,10 @@
10
10
  "off": "Выкл."
11
11
  },
12
12
  "preset_mode": {
13
- "Auto": "Автоматическая",
14
- "Silent": "Тихая",
15
- "Favorite": "Предпочтительная",
16
- "Fan": "Максимальная"
13
+ "auto": "Автоматическая",
14
+ "silent": "Тихая",
15
+ "favorite": "Предпочтительная",
16
+ "fan": "Максимальная"
17
17
  },
18
18
  "error": {
19
19
  "missing_entity": "Требуется указать сущность!"