purifier-card 2.0.0 → 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.0",
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,40 +181,32 @@ 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
190
  <div class="preset-mode">
186
- <paper-menu-button
187
- slot="dropdown-trigger"
188
- .horizontalAlign=${'right'}
189
- .verticalAlign=${'top'}
190
- .verticalOffset=${40}
191
- .noAnimations=${true}
192
- @click="${(e) => e.stopPropagation()}"
193
- >
194
- <paper-button slot="dropdown-trigger">
191
+ <ha-button-menu @click="${(e) => e.stopPropagation()}">
192
+ <mmp-icon-button slot="trigger">
195
193
  <ha-icon icon="mdi:fan"></ha-icon>
196
- <span show=${true}
197
- >${localize(`preset_mode.${preset_mode}`) || preset_mode}
194
+ <span>
195
+ ${localize(`preset_mode.${preset_mode}`) || preset_mode}
198
196
  </span>
199
- </paper-button>
200
- <paper-listbox
201
- slot="dropdown-content"
202
- selected=${selected}
203
- @click="${(e) => this.handlePresetMode(e)}"
204
- >
205
- ${preset_modes.map(
206
- (item) =>
207
- html`<paper-item value=${item}
208
- >${localize(`preset_mode.${item}`) || item}</paper-item
209
- >`
210
- )}
211
- </paper-listbox>
212
- </paper-menu-button>
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>
213
210
  </div>
214
211
  `;
215
212
  }
@@ -245,7 +242,7 @@ class PurifierCard extends LitElement {
245
242
  } = this.entity;
246
243
 
247
244
  const disabled = state !== 'on';
248
- const stateClass = !disabled ? 'working' : 'standby';
245
+ const image = !disabled ? workingImg : standbyImg;
249
246
 
250
247
  return html`
251
248
  <div class="slider">
@@ -256,7 +253,8 @@ class PurifierCard extends LitElement {
256
253
  @value-changed=${(e) => this.handlePercentage(e)}
257
254
  >
258
255
  </round-slider>
259
- <div class="slider-center image ${stateClass}">
256
+ <img src=${image} alt="purifier is ${state}" class="image" />
257
+ <div class="slider-center">
260
258
  <div class="slider-content">
261
259
  ${this.renderAQI()}
262
260
  </div>
@@ -278,7 +276,7 @@ class PurifierCard extends LitElement {
278
276
  } = this.entity;
279
277
 
280
278
  if (!this.showName) {
281
- return html``;
279
+ return nothing;
282
280
  }
283
281
 
284
282
  return html` <div class="friendly-name">${friendly_name}</div> `;
@@ -289,7 +287,7 @@ class PurifierCard extends LitElement {
289
287
  const localizedState = localize(`state.${state}`) || state;
290
288
 
291
289
  if (!this.showState) {
292
- return html``;
290
+ return nothing;
293
291
  }
294
292
 
295
293
  return html`
@@ -297,10 +295,10 @@ class PurifierCard extends LitElement {
297
295
  <span class="state-text" alt=${localizedState}>
298
296
  ${localizedState}
299
297
  </span>
300
- <ha-circular-progress
301
- .active=${this.requestInProgress}
302
- size="small"
303
- ></ha-circular-progress>
298
+ <mwc-circular-progress
299
+ .indeterminate=${this.requestInProgress}
300
+ density="-5"
301
+ ></mwc-circular-progress>
304
302
  </div>
305
303
  `;
306
304
  }
@@ -310,23 +308,33 @@ class PurifierCard extends LitElement {
310
308
 
311
309
  const statsList = stats || [];
312
310
 
313
- return statsList.map(({ entity_id, attribute, unit, subtitle }) => {
314
- if (!entity_id && !attribute) {
315
- return html``;
316
- }
317
-
318
- const value = entity_id
319
- ? this.hass.states[entity_id].state
320
- : 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
+ `;
321
328
 
322
- return html`
323
- <div class="stats-block">
324
- <span class="stats-value">${value}</span>
325
- ${unit}
326
- <div class="stats-subtitle">${subtitle}</div>
327
- </div>
328
- `;
329
- });
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
+ );
330
338
  }
331
339
 
332
340
  renderToolbar() {
@@ -334,7 +342,7 @@ class PurifierCard extends LitElement {
334
342
  const { state, attributes } = this.entity;
335
343
 
336
344
  if (!this.showToolbar) {
337
- return html``;
345
+ return nothing;
338
346
  }
339
347
 
340
348
  const buttons = shortcuts.map(
@@ -408,7 +416,9 @@ class PurifierCard extends LitElement {
408
416
  <ha-card>
409
417
  <div class="preview">
410
418
  <div class="header">
411
- ${this.renderPresetMode()}
419
+ <div class="tips">
420
+ ${this.renderPresetMode()}
421
+ </div>
412
422
  <ha-icon-button
413
423
  class="more-info"
414
424
  icon="mdi:dots-vertical"
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
- }