purifier-card 2.3.0 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "purifier-card",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Air Purifier card for Home Assistant Lovelace UI",
5
5
  "main": "dist/purifier-card.js",
6
6
  "scripts": {
@@ -24,9 +24,12 @@
24
24
  "author": "Denys Dovhan <denysdovhan@gmail.com> (http://denysdovhan.com/)",
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
+ "@types/lodash": "^4.14.195",
27
28
  "custom-card-helpers": "^1.6.4",
28
- "ha-template": "^1.0.5",
29
- "lit": "^2.2.2"
29
+ "ha-template": "^1.2.0",
30
+ "home-assistant-js-websocket": "^8.0.1",
31
+ "lit": "^2.2.2",
32
+ "lodash": "^4.17.21"
30
33
  },
31
34
  "devDependencies": {
32
35
  "@babel/core": "^7.9.6",
@@ -37,7 +40,10 @@
37
40
  "@rollup/plugin-image": "^3.0.1",
38
41
  "@rollup/plugin-json": "^6.0.0",
39
42
  "@rollup/plugin-node-resolve": "^15.0.1",
43
+ "@rollup/plugin-replace": "^5.0.2",
40
44
  "@semantic-release/git": "^10.0.1",
45
+ "@typescript-eslint/eslint-plugin": "^5.59.11",
46
+ "@typescript-eslint/parser": "^5.59.11",
41
47
  "eslint": "^8.14.0",
42
48
  "eslint-config-prettier": "^8.5.0",
43
49
  "eslint-plugin-import": "^2.20.2",
@@ -51,7 +57,9 @@
51
57
  "rollup-plugin-postcss-lit": "^2.0.0",
52
58
  "rollup-plugin-serve": "^2.0.1",
53
59
  "rollup-plugin-terser": "^7.0.2",
54
- "semantic-release": "^19.0.2"
60
+ "rollup-plugin-typescript2": "^0.34.1",
61
+ "semantic-release": "^19.0.2",
62
+ "typescript": "^5.1.3"
55
63
  },
56
64
  "browserslist": [
57
65
  "last 2 versions",
package/rollup.config.js CHANGED
@@ -1,7 +1,9 @@
1
1
  /* eslint-env node */
2
+ import pkg from './package.json';
2
3
  import commonjs from '@rollup/plugin-commonjs';
3
4
  import nodeResolve from '@rollup/plugin-node-resolve';
4
5
  import json from '@rollup/plugin-json';
6
+ import typescript from 'rollup-plugin-typescript2';
5
7
  import babel from '@rollup/plugin-babel';
6
8
  import image from '@rollup/plugin-image';
7
9
  import postcss from 'rollup-plugin-postcss';
@@ -9,6 +11,7 @@ import postcssPresetEnv from 'postcss-preset-env';
9
11
  import postcssLit from 'rollup-plugin-postcss-lit';
10
12
  import { terser } from 'rollup-plugin-terser';
11
13
  import minifyLiterals from 'rollup-plugin-minify-html-literals';
14
+ import replace from '@rollup/plugin-replace';
12
15
  import serve from 'rollup-plugin-serve';
13
16
 
14
17
  const IS_DEV = process.env.ROLLUP_WATCH;
@@ -23,40 +26,52 @@ const serverOptions = {
23
26
  },
24
27
  };
25
28
 
29
+ const plugins = [
30
+ nodeResolve(),
31
+ commonjs(),
32
+ json(),
33
+ replace({
34
+ values: {
35
+ PKG_VERSION_VALUE: IS_DEV ? 'DEVELOPMENT' : pkg.version,
36
+ },
37
+ preventAssignment: true,
38
+ }),
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
+ image(),
53
+ typescript(),
54
+ babel({
55
+ babelHelpers: 'runtime',
56
+ exclude: 'node_modules/**',
57
+ }),
58
+ IS_DEV && serve(serverOptions),
59
+ !IS_DEV && minifyLiterals(),
60
+ !IS_DEV &&
61
+ terser({
62
+ output: {
63
+ comments: false,
64
+ },
65
+ }),
66
+ ];
67
+
26
68
  export default {
27
- input: 'src/purifier-card.js',
69
+ input: 'src/purifier-card.ts',
28
70
  output: {
29
71
  dir: 'dist',
30
72
  format: 'es',
73
+ inlineDynamicImports: true,
31
74
  },
32
- plugins: [
33
- nodeResolve(),
34
- commonjs(),
35
- json(),
36
- babel({
37
- babelHelpers: 'runtime',
38
- exclude: 'node_modules/**',
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
- image(),
53
- IS_DEV && serve(serverOptions),
54
- !IS_DEV && minifyLiterals(),
55
- !IS_DEV &&
56
- terser({
57
- output: {
58
- comments: false,
59
- },
60
- }),
61
- ],
75
+ context: 'window',
76
+ plugins,
62
77
  };
package/src/config.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { PurifierCardConfig } from './types';
2
+ import localize from './localize';
3
+
4
+ export default function buildConfig(
5
+ config?: Partial<PurifierCardConfig>
6
+ ): PurifierCardConfig {
7
+ if (!config) {
8
+ throw new Error(localize('error.invalid_config'));
9
+ }
10
+
11
+ if (!config.entity) {
12
+ throw new Error(localize('error.missing_entity'));
13
+ }
14
+
15
+ return {
16
+ entity: config.entity,
17
+ show_name: config.show_name ?? true,
18
+ show_state: config.show_state ?? true,
19
+ show_preset_mode: config.show_preset_mode ?? true,
20
+ show_toolbar: config.show_toolbar ?? true,
21
+ compact_view: config.compact_view ?? false,
22
+ aqi: config.aqi ?? {},
23
+ stats: config.stats ?? [],
24
+ shortcuts: config.shortcuts ?? [],
25
+ };
26
+ }
@@ -0,0 +1,5 @@
1
+ // declaration.d.ts
2
+ declare module '*.css';
3
+ declare module '*.svg';
4
+ declare module '*.png';
5
+ declare module '*.gif';
package/src/editor.css ADDED
@@ -0,0 +1,19 @@
1
+ .card-config {
2
+ flex-direction: column;
3
+ display: flex;
4
+ gap: 10px;
5
+ }
6
+
7
+ .option {
8
+ display: flex;
9
+ align-items: center;
10
+ }
11
+
12
+ .option ha-switch {
13
+ margin-right: 10px;
14
+ }
15
+
16
+ .option ha-select,
17
+ .option paper-input {
18
+ width: 100%;
19
+ }
package/src/editor.ts ADDED
@@ -0,0 +1,166 @@
1
+ import { LitElement, html, nothing } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+ import {
4
+ HomeAssistant,
5
+ LovelaceCardConfig,
6
+ fireEvent,
7
+ } from 'custom-card-helpers';
8
+ import { PurifierCardConfig, Template } from './types';
9
+ import localize from './localize';
10
+ import styles from './editor.css';
11
+
12
+ type ConfigElement = HTMLInputElement & {
13
+ configValue?: keyof PurifierCardConfig;
14
+ };
15
+
16
+ @customElement('purifier-card-editor')
17
+ export class PurifierCardEditor extends LitElement {
18
+ @property({ attribute: false }) public hass?: HomeAssistant;
19
+
20
+ @state() private config!: Partial<PurifierCardConfig>;
21
+
22
+ @state() private compact_view = false;
23
+ @state() private show_name = true;
24
+ @state() private show_state = true;
25
+ @state() private show_toolbar = true;
26
+
27
+ public setConfig(config: LovelaceCardConfig & PurifierCardConfig) {
28
+ this.config = config;
29
+
30
+ if (!this.config.entity) {
31
+ this.config.entity = this.getEntitiesByType('fan')[0] || '';
32
+ fireEvent(this, 'config-changed', { config: this.config });
33
+ }
34
+ }
35
+
36
+ private getEntitiesByType(type: string): string[] {
37
+ if (!this.hass) {
38
+ return [];
39
+ }
40
+
41
+ return Object.keys(this.hass.states).filter((eid) => eid.startsWith(type));
42
+ }
43
+
44
+ protected render(): Template {
45
+ if (!this.hass) {
46
+ return nothing;
47
+ }
48
+
49
+ const fanEntities = this.getEntitiesByType('fan');
50
+
51
+ return html`
52
+ <div class="card-config">
53
+ <div class="option">
54
+ <ha-select
55
+ .label=${localize('editor.entity')}
56
+ @selected=${this.valueChanged}
57
+ .configValue=${'entity'}
58
+ .value=${this.config.entity}
59
+ @closed=${(e: PointerEvent) => e.stopPropagation()}
60
+ fixedMenuPosition
61
+ naturalMenuWidth
62
+ required
63
+ validationMessage=${localize('error.missing_entity')}
64
+ >
65
+ ${fanEntities.map(
66
+ (entity) => html`<mwc-list-item .value=${entity}
67
+ >${entity}</mwc-list-item
68
+ >`
69
+ )}
70
+ </ha-select>
71
+ </div>
72
+
73
+ <div class="option">
74
+ <ha-switch
75
+ aria-label=${localize(
76
+ this.compact_view
77
+ ? 'editor.compact_view_aria_label_off'
78
+ : 'editor.compact_view_aria_label_on'
79
+ )}
80
+ .checked=${this.compact_view}
81
+ .configValue=${'compact_view'}
82
+ @change=${this.valueChanged}
83
+ >
84
+ </ha-switch>
85
+ ${localize('editor.compact_view')}
86
+ </div>
87
+
88
+ <div class="option">
89
+ <ha-switch
90
+ aria-label=${localize(
91
+ this.show_name
92
+ ? 'editor.show_name_aria_label_off'
93
+ : 'editor.show_name_aria_label_on'
94
+ )}
95
+ .checked=${this.show_name}
96
+ .configValue=${'show_name'}
97
+ @change=${this.valueChanged}
98
+ >
99
+ </ha-switch>
100
+ ${localize('editor.show_name')}
101
+ </div>
102
+
103
+ <div class="option">
104
+ <ha-switch
105
+ aria-label=${localize(
106
+ this.show_state
107
+ ? 'editor.show_state_aria_label_off'
108
+ : 'editor.show_state_aria_label_on'
109
+ )}
110
+ .checked=${this.show_state}
111
+ .configValue=${'show_state'}
112
+ @change=${this.valueChanged}
113
+ >
114
+ </ha-switch>
115
+ ${localize('editor.show_state')}
116
+ </div>
117
+
118
+ <div class="option">
119
+ <ha-switch
120
+ aria-label=${localize(
121
+ this.show_name
122
+ ? 'editor.show_toolbar_aria_label_off'
123
+ : 'editor.show_toolbar_aria_label_on'
124
+ )}
125
+ .checked=${this.show_toolbar}
126
+ .configValue=${'show_toolbar'}
127
+ @change=${this.valueChanged}
128
+ >
129
+ </ha-switch>
130
+ ${localize('editor.show_toolbar')}
131
+ </div>
132
+
133
+ <strong> ${localize('editor.code_only_note')}</strong>
134
+ </div>
135
+ `;
136
+ }
137
+
138
+ private valueChanged(event: Event): void {
139
+ if (!this.config || !this.hass || !event.target) {
140
+ return;
141
+ }
142
+ const target = event.target as ConfigElement;
143
+ if (
144
+ !target.configValue ||
145
+ this.config[target.configValue] === target?.value
146
+ ) {
147
+ return;
148
+ }
149
+ if (target.configValue) {
150
+ if (target.value === '') {
151
+ delete this.config[target.configValue];
152
+ } else {
153
+ this.config = {
154
+ ...this.config,
155
+ [target.configValue]:
156
+ target.checked !== undefined ? target.checked : target.value,
157
+ };
158
+ }
159
+ }
160
+ fireEvent(this, 'config-changed', { config: this.config });
161
+ }
162
+
163
+ static get styles() {
164
+ return styles;
165
+ }
166
+ }
@@ -17,7 +17,13 @@ import * as it from './translations/it.json';
17
17
  import * as cs from './translations/cs.json';
18
18
  import * as nl from './translations/nl.json';
19
19
 
20
- var languages = {
20
+ type Translations = {
21
+ [key: string]: {
22
+ [key: string]: string;
23
+ };
24
+ };
25
+
26
+ const languages: Record<string, Translations> = {
21
27
  en,
22
28
  uk,
23
29
  fr,
@@ -37,13 +43,17 @@ var languages = {
37
43
 
38
44
  const DEFAULT_LANG = 'en';
39
45
 
40
- export default function localize(string, search, replace) {
41
- const [section, key] = string.split('.');
46
+ export default function localize(
47
+ str: string,
48
+ search?: string,
49
+ replace?: string
50
+ ): string | undefined {
51
+ const [section, key] = str.toLowerCase().split('.');
42
52
 
43
- let langStored;
53
+ let langStored: string | null = null;
44
54
 
45
55
  try {
46
- langStored = JSON.parse(localStorage.getItem('selectedLanguage'));
56
+ langStored = JSON.parse(localStorage.getItem('selectedLanguage') ?? '');
47
57
  } catch (e) {
48
58
  langStored = localStorage.getItem('selectedLanguage');
49
59
  }
@@ -57,23 +67,17 @@ export default function localize(string, search, replace) {
57
67
  try {
58
68
  translated = languages[lang][section][key];
59
69
  } catch (e) {
60
- /**/
70
+ translated = languages[DEFAULT_LANG][section][key];
61
71
  }
62
-
63
72
  if (translated === undefined) {
64
- try {
65
- translated = languages[DEFAULT_LANG][section][key];
66
- } catch (e) {
67
- /**/
68
- }
73
+ translated = languages[DEFAULT_LANG][section][key];
69
74
  }
70
-
71
75
  if (translated === undefined) {
72
76
  return;
73
77
  }
74
78
 
75
- if (search !== '' && replace !== '') {
76
- translated = translated.replace(search, replace);
79
+ if (search && replace) {
80
+ translated = translated?.replace(search, replace);
77
81
  }
78
82
 
79
83
  return translated;