purifier-card 2.0.2 → 2.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "purifier-card",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
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.2",
28
+ "lit": "^2.2.2"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@babel/core": "^7.9.6",
@@ -39,8 +40,12 @@
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"
@@ -54,6 +59,9 @@
54
59
  "*.js": "eslint --fix",
55
60
  "*": "prettier --write"
56
61
  },
62
+ "prettier": {
63
+ "singleQuote": true
64
+ },
57
65
  "release": {
58
66
  "plugins": [
59
67
  "@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,33 @@ 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`<mwc-list-item
202
+ ?activated=${selected === index}
203
+ value=${item}
204
+ @click=${(e) => this.handlePresetMode(e)}
205
+ >
206
+ ${localize(`preset_mode.${item}`) || item}
207
+ </mwc-list-item>`
208
+ )}
209
+ </ha-button-menu>
210
+ </div>
204
211
  `;
205
212
  }
206
213
 
@@ -235,7 +242,7 @@ class PurifierCard extends LitElement {
235
242
  } = this.entity;
236
243
 
237
244
  const disabled = state !== 'on';
238
- const stateClass = !disabled ? 'working' : 'standby';
245
+ const image = !disabled ? workingImg : standbyImg;
239
246
 
240
247
  return html`
241
248
  <div class="slider">
@@ -246,7 +253,8 @@ class PurifierCard extends LitElement {
246
253
  @value-changed=${(e) => this.handlePercentage(e)}
247
254
  >
248
255
  </round-slider>
249
- <div class="slider-center image ${stateClass}">
256
+ <img src=${image} alt="purifier is ${state}" class="image" />
257
+ <div class="slider-center">
250
258
  <div class="slider-content">
251
259
  ${this.renderAQI()}
252
260
  </div>
@@ -268,7 +276,7 @@ class PurifierCard extends LitElement {
268
276
  } = this.entity;
269
277
 
270
278
  if (!this.showName) {
271
- return html``;
279
+ return nothing;
272
280
  }
273
281
 
274
282
  return html` <div class="friendly-name">${friendly_name}</div> `;
@@ -279,7 +287,7 @@ class PurifierCard extends LitElement {
279
287
  const localizedState = localize(`state.${state}`) || state;
280
288
 
281
289
  if (!this.showState) {
282
- return html``;
290
+ return nothing;
283
291
  }
284
292
 
285
293
  return html`
@@ -287,10 +295,10 @@ class PurifierCard extends LitElement {
287
295
  <span class="state-text" alt=${localizedState}>
288
296
  ${localizedState}
289
297
  </span>
290
- <ha-circular-progress
291
- .active=${this.requestInProgress}
292
- size="small"
293
- ></ha-circular-progress>
298
+ <mwc-circular-progress
299
+ .indeterminate=${this.requestInProgress}
300
+ density="-5"
301
+ ></mwc-circular-progress>
294
302
  </div>
295
303
  `;
296
304
  }
@@ -300,23 +308,33 @@ class PurifierCard extends LitElement {
300
308
 
301
309
  const statsList = stats || [];
302
310
 
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];
311
+ return statsList.map(
312
+ ({ entity_id, attribute, value_template, unit, subtitle }) => {
313
+ if (!entity_id && !attribute && !value_template) {
314
+ return nothing;
315
+ }
316
+
317
+ const state = entity_id
318
+ ? this.hass.states[entity_id].state
319
+ : this.entity.attributes[attribute];
320
+
321
+ const value = html`
322
+ <ha-template
323
+ hass=${this.hass}
324
+ template=${value_template}
325
+ value=${state}
326
+ ></ha-template>
327
+ `;
311
328
 
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
- });
329
+ return html`
330
+ <div class="stats-block" @click="${() => this.handleMore(entity_id)}">
331
+ <span class="stats-value">${value}</span>
332
+ ${unit}
333
+ <div class="stats-subtitle">${subtitle}</div>
334
+ </div>
335
+ `;
336
+ }
337
+ );
320
338
  }
321
339
 
322
340
  renderToolbar() {
@@ -324,7 +342,7 @@ class PurifierCard extends LitElement {
324
342
  const { state, attributes } = this.entity;
325
343
 
326
344
  if (!this.showToolbar) {
327
- return html``;
345
+ return nothing;
328
346
  }
329
347
 
330
348
  const buttons = shortcuts.map(
@@ -398,7 +416,7 @@ class PurifierCard extends LitElement {
398
416
  <ha-card>
399
417
  <div class="preview">
400
418
  <div class="header">
401
- <div class="preset-mode">
419
+ <div class="tips">
402
420
  ${this.renderPresetMode()}
403
421
  </div>
404
422
  <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
- }