purifier-card 2.0.2 → 2.1.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "purifier-card",
3
- "version": "2.0.2",
3
+ "version": "2.1.2",
4
4
  "description": "Air Purifier card for Home Assistant Lovelace UI",
5
5
  "main": "dist/purifier-card.js",
6
6
  "scripts": {
@@ -24,7 +24,8 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "custom-card-helpers": "^1.6.4",
27
- "lit-element": "^2.3.1"
27
+ "ha-template": "^1.0.3",
28
+ "lit": "^2.2.2"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@babel/core": "^7.9.6",
@@ -39,12 +40,20 @@
39
40
  "eslint-plugin-import": "^2.20.2",
40
41
  "husky": "^4.2.5",
41
42
  "lint-staged": "^10.2.2",
43
+ "postcss-preset-env": "^7.4.3",
42
44
  "prettier": "^2.0.5",
43
45
  "rollup": "^2.7.6",
46
+ "rollup-plugin-minify-html-literals": "^1.2.6",
47
+ "rollup-plugin-postcss": "^4.0.2",
48
+ "rollup-plugin-postcss-lit": "^2.0.0",
44
49
  "rollup-plugin-serve": "^1.0.1",
45
50
  "rollup-plugin-terser": "^7.0.2",
46
51
  "semantic-release": "^17.3.7"
47
52
  },
53
+ "browserslist": [
54
+ "last 2 versions",
55
+ "not dead"
56
+ ],
48
57
  "husky": {
49
58
  "hooks": {
50
59
  "pre-commit": "lint-staged"
@@ -54,6 +63,9 @@
54
63
  "*.js": "eslint --fix",
55
64
  "*": "prettier --write"
56
65
  },
66
+ "prettier": {
67
+ "singleQuote": true
68
+ },
57
69
  "release": {
58
70
  "plugins": [
59
71
  "@semantic-release/commit-analyzer",
package/rollup.config.js CHANGED
@@ -4,7 +4,11 @@ import nodeResolve from '@rollup/plugin-node-resolve';
4
4
  import json from '@rollup/plugin-json';
5
5
  import babel from '@rollup/plugin-babel';
6
6
  import image from '@rollup/plugin-image';
7
+ import postcss from 'rollup-plugin-postcss';
8
+ import postcssPresetEnv from 'postcss-preset-env';
9
+ import postcssLit from 'rollup-plugin-postcss-lit';
7
10
  import { terser } from 'rollup-plugin-terser';
11
+ import minifyLiterals from 'rollup-plugin-minify-html-literals';
8
12
  import serve from 'rollup-plugin-serve';
9
13
 
10
14
  const IS_DEV = process.env.ROLLUP_WATCH;
@@ -30,10 +34,31 @@ export default {
30
34
  commonjs(),
31
35
  json(),
32
36
  babel({
37
+ babelHelpers: 'bundled',
33
38
  exclude: 'node_modules/**',
34
39
  }),
40
+ postcss({
41
+ plugins: [
42
+ postcssPresetEnv({
43
+ stage: 1,
44
+ features: {
45
+ 'nesting-rules': true,
46
+ },
47
+ }),
48
+ ],
49
+ extract: false,
50
+ }),
51
+ postcssLit({
52
+ importPackage: 'lit-element',
53
+ }),
35
54
  image(),
36
55
  IS_DEV && serve(serverOptions),
37
- !IS_DEV && terser(),
56
+ !IS_DEV && minifyLiterals(),
57
+ !IS_DEV &&
58
+ terser({
59
+ output: {
60
+ comments: false,
61
+ },
62
+ }),
38
63
  ],
39
64
  };
@@ -1,4 +1,4 @@
1
- import { LitElement, html, css } from 'lit-element';
1
+ import { LitElement, html, css, nothing } from 'lit';
2
2
  import { fireEvent } from 'custom-card-helpers';
3
3
  import localize from './localize';
4
4
 
@@ -68,7 +68,7 @@ export class PurifierCardEditor extends LitElement {
68
68
 
69
69
  render() {
70
70
  if (!this.hass) {
71
- return html``;
71
+ return nothing;
72
72
  }
73
73
 
74
74
  const fanEntities = this.getEntitiesByType('fan');
@@ -1,9 +1,14 @@
1
- import { LitElement, html } from 'lit-element';
1
+ import { LitElement, html, nothing } from 'lit';
2
2
  import { hasConfigOrEntityChanged, fireEvent } from 'custom-card-helpers';
3
- import './purifier-card-editor';
3
+ import registerTemplates from 'ha-template';
4
4
  import localize from './localize';
5
- import styles from './styles';
5
+ import styles from './styles.css';
6
6
  import { version } from '../package.json';
7
+ import workingImg from './images/purifier-working.gif';
8
+ import standbyImg from './images/purifier-standby.png';
9
+ import './purifier-card-editor';
10
+
11
+ registerTemplates();
7
12
 
8
13
  console.info(
9
14
  `%c PURIFIER-CARD %c ${version} `,
@@ -129,15 +134,15 @@ class PurifierCard extends LitElement {
129
134
  }
130
135
  }
131
136
 
132
- handleMore() {
137
+ handleMore(entityId = this.entity.entity_id) {
133
138
  fireEvent(
134
139
  this,
135
140
  'hass-more-info',
136
141
  {
137
- entityId: this.entity.entity_id,
142
+ entityId,
138
143
  },
139
144
  {
140
- bubbles: true,
145
+ bubbles: false,
141
146
  composed: true,
142
147
  }
143
148
  );
@@ -176,31 +181,35 @@ class PurifierCard extends LitElement {
176
181
  !preset_modes ||
177
182
  !(supported_features & SUPPORT_PRESET_MODE)
178
183
  ) {
179
- return html``;
184
+ return nothing;
180
185
  }
181
186
 
182
187
  const selected = preset_modes.indexOf(preset_mode);
183
188
 
184
189
  return html`
185
- <ha-button-menu @click="${(e) => e.stopPropagation()}">
186
- <mmp-icon-button slot="trigger">
187
- <ha-icon icon="mdi:fan"></ha-icon>
188
- <span>
189
- ${localize(`preset_mode.${preset_mode}`) || preset_mode}
190
- </span>
191
- </mmp-icon-button>
192
-
193
- ${preset_modes.map(
194
- (item, index) =>
195
- html`<mwc-list-item
196
- ?activated=${selected === index}
197
- value=${item}
198
- @click=${(e) => this.handlePresetMode(e)}
199
- >
200
- ${localize(`preset_mode.${item}`) || item}
201
- </mwc-list-item>`
202
- )}
203
- </ha-button-menu>
190
+ <div class="preset-mode">
191
+ <ha-button-menu @click="${(e) => e.stopPropagation()}">
192
+ <mmp-icon-button slot="trigger">
193
+ <ha-icon icon="mdi:fan"></ha-icon>
194
+ <span>
195
+ ${localize(`preset_mode.${preset_mode}`) || preset_mode}
196
+ </span>
197
+ </mmp-icon-button>
198
+
199
+ ${preset_modes.map(
200
+ (item, index) =>
201
+ html`
202
+ <mwc-list-item
203
+ ?activated=${selected === index}
204
+ value=${item}
205
+ @click=${(e) => this.handlePresetMode(e)}
206
+ >
207
+ ${localize(`preset_mode.${item}`) || item}
208
+ </mwc-list-item>
209
+ `
210
+ )}
211
+ </ha-button-menu>
212
+ </div>
204
213
  `;
205
214
  }
206
215
 
@@ -235,7 +244,7 @@ class PurifierCard extends LitElement {
235
244
  } = this.entity;
236
245
 
237
246
  const disabled = state !== 'on';
238
- const stateClass = !disabled ? 'working' : 'standby';
247
+ const image = !disabled ? workingImg : standbyImg;
239
248
 
240
249
  return html`
241
250
  <div class="slider">
@@ -246,12 +255,13 @@ class PurifierCard extends LitElement {
246
255
  @value-changed=${(e) => this.handlePercentage(e)}
247
256
  >
248
257
  </round-slider>
249
- <div class="slider-center image ${stateClass}">
258
+ <img src=${image} alt="purifier is ${state}" class="image" />
259
+ <div class="slider-center">
250
260
  <div class="slider-content">
251
261
  ${this.renderAQI()}
252
262
  </div>
253
263
  <div class="slider-value">
254
- ${percentage}%
264
+ ${percentage ? `${percentage}%` : nothing}
255
265
  </div>
256
266
  </div>
257
267
  </div>
@@ -268,7 +278,7 @@ class PurifierCard extends LitElement {
268
278
  } = this.entity;
269
279
 
270
280
  if (!this.showName) {
271
- return html``;
281
+ return nothing;
272
282
  }
273
283
 
274
284
  return html` <div class="friendly-name">${friendly_name}</div> `;
@@ -279,7 +289,7 @@ class PurifierCard extends LitElement {
279
289
  const localizedState = localize(`state.${state}`) || state;
280
290
 
281
291
  if (!this.showState) {
282
- return html``;
292
+ return nothing;
283
293
  }
284
294
 
285
295
  return html`
@@ -287,10 +297,10 @@ class PurifierCard extends LitElement {
287
297
  <span class="state-text" alt=${localizedState}>
288
298
  ${localizedState}
289
299
  </span>
290
- <ha-circular-progress
291
- .active=${this.requestInProgress}
292
- size="small"
293
- ></ha-circular-progress>
300
+ <mwc-circular-progress
301
+ .indeterminate=${this.requestInProgress}
302
+ density="-5"
303
+ ></mwc-circular-progress>
294
304
  </div>
295
305
  `;
296
306
  }
@@ -300,23 +310,33 @@ class PurifierCard extends LitElement {
300
310
 
301
311
  const statsList = stats || [];
302
312
 
303
- return statsList.map(({ entity_id, attribute, unit, subtitle }) => {
304
- if (!entity_id && !attribute) {
305
- return html``;
306
- }
307
-
308
- const value = entity_id
309
- ? this.hass.states[entity_id].state
310
- : this.entity.attributes[attribute];
313
+ return statsList.map(
314
+ ({ entity_id, attribute, value_template, unit, subtitle }) => {
315
+ if (!entity_id && !attribute && !value_template) {
316
+ return nothing;
317
+ }
318
+
319
+ const state = entity_id
320
+ ? this.hass.states[entity_id].state
321
+ : this.entity.attributes[attribute];
322
+
323
+ const value = html`
324
+ <ha-template
325
+ hass=${this.hass}
326
+ template=${value_template}
327
+ value=${state}
328
+ ></ha-template>
329
+ `;
311
330
 
312
- return html`
313
- <div class="stats-block">
314
- <span class="stats-value">${value}</span>
315
- ${unit}
316
- <div class="stats-subtitle">${subtitle}</div>
317
- </div>
318
- `;
319
- });
331
+ return html`
332
+ <div class="stats-block" @click="${() => this.handleMore(entity_id)}">
333
+ <span class="stats-value">${value}</span>
334
+ ${unit}
335
+ <div class="stats-subtitle">${subtitle}</div>
336
+ </div>
337
+ `;
338
+ }
339
+ );
320
340
  }
321
341
 
322
342
  renderToolbar() {
@@ -324,7 +344,7 @@ class PurifierCard extends LitElement {
324
344
  const { state, attributes } = this.entity;
325
345
 
326
346
  if (!this.showToolbar) {
327
- return html``;
347
+ return nothing;
328
348
  }
329
349
 
330
350
  const buttons = shortcuts.map(
@@ -398,7 +418,7 @@ class PurifierCard extends LitElement {
398
418
  <ha-card>
399
419
  <div class="preview">
400
420
  <div class="header">
401
- <div class="preset-mode">
421
+ <div class="tips">
402
422
  ${this.renderPresetMode()}
403
423
  </div>
404
424
  <ha-icon-button
package/src/styles.css ADDED
@@ -0,0 +1,260 @@
1
+ :host {
2
+ --pc-background: var(
3
+ --ha-card-background,
4
+ var(--card-background-color, white)
5
+ );
6
+ --pc-primary-text-color: var(--primary-text-color);
7
+ --pc-secondary-text-color: var(--secondary-text-color);
8
+ --pc-icon-color: var(--secondary-text-color);
9
+ --pc-slider-path-color: var(--round-slider-path-color);
10
+ --pc-slider-bar-color: var(--round-slider-bar-color);
11
+ --pc-toolbar-background: var(--vc-background);
12
+ --pc-toolbar-icon-color: var(--secondary-text-color);
13
+ --pc-divider-color: var(--entities-divider-color, var(--divider-color));
14
+ --pc-spacing: 10px;
15
+
16
+ display: flex;
17
+ flex: 1;
18
+ flex-direction: column;
19
+ }
20
+
21
+ ha-card {
22
+ flex-direction: column;
23
+ flex: 1;
24
+ position: relative;
25
+ padding: 0px;
26
+ overflow: hidden;
27
+ }
28
+
29
+ .fill-gap {
30
+ flex-grow: 1;
31
+ }
32
+
33
+ .preview {
34
+ background-color: var(--pc-background);
35
+ overflow: hidden;
36
+ position: relative;
37
+ }
38
+
39
+ .header {
40
+ display: flex;
41
+ justify-content: space-between;
42
+ color: var(--pc-primary-text-color);
43
+ }
44
+
45
+ .tips {
46
+ display: flex;
47
+ gap: var(--pc-spacing);
48
+ flex-grow: 1;
49
+ flex-wrap: wrap;
50
+ padding: var(--pc-spacing);
51
+
52
+ & .tip {
53
+ cursor: pointer;
54
+ }
55
+ }
56
+
57
+ .preset-mode ha-icon {
58
+ display: inline-block;
59
+ }
60
+
61
+ .more-info {
62
+ color: var(--pc-primary-text-color);
63
+ }
64
+
65
+ .controls {
66
+ display: flex;
67
+ justify-content: center;
68
+ }
69
+
70
+ .slider {
71
+ height: 100%;
72
+ width: 100%;
73
+ position: relative;
74
+ max-width: 250px;
75
+ min-width: 100px;
76
+ }
77
+
78
+ .slider round-slider {
79
+ --round-slider-path-color: var(--pc-slider-path-color);
80
+ --round-slider-bar-color: var(--pc-slider-bar-color);
81
+ }
82
+
83
+ .slider-center {
84
+ position: absolute;
85
+ width: calc(100% - 90px);
86
+ height: calc(100% - 10px);
87
+ box-sizing: border-box;
88
+ border-radius: 100%;
89
+ left: 45px;
90
+ top: 20px;
91
+ text-align: center;
92
+ overflow-wrap: break-word;
93
+ pointer-events: none;
94
+ }
95
+
96
+ .slider-content {
97
+ position: absolute;
98
+ transform: translate(-50%, -50%);
99
+ width: 100%;
100
+ top: 50%;
101
+ left: 50%;
102
+ }
103
+
104
+ .slider-value {
105
+ position: absolute;
106
+ bottom: 0;
107
+ left: 0;
108
+ width: 100%;
109
+ transform: translateY(-50%);
110
+ font-size: 16px;
111
+ color: var(--pc-primary-text-color);
112
+ }
113
+
114
+ .image {
115
+ height: 100%;
116
+ width: 100%;
117
+ position: absolute;
118
+ top: 0;
119
+ left: 0;
120
+ object-fit: contain;
121
+ pointer-events: none;
122
+ }
123
+
124
+ .preview.not-available {
125
+ filter: grayscale(1);
126
+ }
127
+
128
+ .number-off {
129
+ opacity: 0.2;
130
+ }
131
+
132
+ .current-aqi {
133
+ font-size: 48px;
134
+ font-weight: bold;
135
+ line-height: 48px;
136
+ padding: var(--pc-spacing);
137
+ border-radius: 4px;
138
+ background: rgba(0, 0, 0, 0.6);
139
+ color: var(--text-primary-color);
140
+ }
141
+
142
+ .current-aqi sup {
143
+ font-size: 16px;
144
+ line-height: 16px;
145
+ font-weight: normal;
146
+ }
147
+
148
+ .state {
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ }
153
+
154
+ .state-text {
155
+ color: var(--pc-secondary-text-color);
156
+ white-space: nowrap;
157
+ text-overflow: ellipsis;
158
+ overflow: hidden;
159
+ margin-left: calc(28px + var(--pc-spacing)); /* size + margin of spinner */
160
+ }
161
+
162
+ .state mwc-circular-progress {
163
+ --mdc-theme-primary: var(--pc-secondary-text-color) !important;
164
+ margin-left: var(--pc-spacing);
165
+ }
166
+
167
+ .friendly-name {
168
+ text-align: center;
169
+ font-weight: bold;
170
+ color: var(--pc-primary-text-colo);
171
+ font-size: 16px;
172
+ }
173
+
174
+ .not-available {
175
+ text-align: center;
176
+ color: var(--pc-primary-text-colo);
177
+ font-size: 16px;
178
+ }
179
+
180
+ .metadata {
181
+ margin: var(--pc-spacing) auto;
182
+ }
183
+
184
+ .stats {
185
+ border-top: 1px solid var(--pc-divider-color);
186
+ display: flex;
187
+ flex-direction: row;
188
+ justify-content: space-evenly;
189
+ color: var(--pc-secondary-text-color);
190
+ }
191
+
192
+ .stats-block {
193
+ cursor: pointer;
194
+ margin: var(--pc-spacing) 0px;
195
+ text-align: center;
196
+ border-right: 1px solid var(--pc-divider-color);
197
+ flex-grow: 1;
198
+
199
+ &:last-of-type {
200
+ border-right: 0px;
201
+ }
202
+ }
203
+
204
+ .stats-value {
205
+ font-size: 20px;
206
+ color: var(--pc-primary-text-color);
207
+ }
208
+
209
+ ha-icon {
210
+ display: flex;
211
+ color: var(--pc-icon-color);
212
+ }
213
+
214
+ .toolbar {
215
+ background: var(--pc-toolbar-background);
216
+ min-height: 30px;
217
+ display: flex;
218
+ flex-direction: row;
219
+ flex-flow: row wrap;
220
+ flex-wrap: wrap;
221
+ justify-content: space-evenly;
222
+ padding: 5px;
223
+ border-top: 1px solid var(--pc-divider-color);
224
+ }
225
+
226
+ .toolbar ha-icon-button {
227
+ color: var(--pc-toolbar-text-color);
228
+ flex-direction: column;
229
+ width: 44px;
230
+ height: 44px;
231
+ --mdc-icon-button-size: 44px;
232
+ opacity: 0.5;
233
+ }
234
+
235
+ .toolbar ha-icon-button.active {
236
+ opacity: 1;
237
+ }
238
+
239
+ .toolbar paper-button {
240
+ color: var(--pc-toolbar-icon-color);
241
+ flex-direction: column;
242
+ margin-right: 10px;
243
+ padding: 15px 10px;
244
+ cursor: pointer;
245
+ }
246
+
247
+ .toolbar ha-icon-button:active,
248
+ .toolbar paper-button:active {
249
+ opacity: 0.4;
250
+ background: rgba(0, 0, 0, 0.1);
251
+ }
252
+
253
+ .toolbar paper-button {
254
+ color: var(--pc-toolbar-icon-color);
255
+ flex-direction: row;
256
+ }
257
+
258
+ .toolbar ha-icon {
259
+ color: var(--pc-toolbar-icon-color);
260
+ }
package/.prettierrc.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "singleQuote": true
3
- }