diff --git a/.eslintrc.json b/.eslintrc.json index 4b4cc1aa..e2484ad2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,7 +6,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 2018, + "ecmaVersion": 2020, "sourceType": "module" } } diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..1534e0ff --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'monthly' + + # Maintain dependencies for npm + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'monthly' + open-pull-requests-limit: 10 diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 544138be..00000000 --- a/.prettierrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "singleQuote": true -} diff --git a/LICENSE.md b/LICENSE.md index 6893c343..bc9c0bcf 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Custom cards for Home Assistant +Copyright (c) 2022 Denys Dovhan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index af73acb5..ae80392a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Just search for `Vacuum Card` in plugins tab. ``` 4. Add `custom:vacuum-card` to Lovelace UI as any other card (using either editor or YAML configuration). -## Using the card +## Usage This card can be configured using Lovelace UI editor. @@ -88,9 +88,10 @@ stats: unit: hours subtitle: Sensors cleaning: - - attribute: cleaned_area - unit: m2 - subtitle: Cleaning area + - value_template: >- + {{ (states('sensor.vacuum_main_brush_left') | float(0) / 3600) | round(1) }} + subtitle: Main brush + unit: hours - attribute: cleaning_time unit: minutes subtitle: Cleaning time @@ -127,12 +128,13 @@ Here is what every option means: You can use any attribute of vacuum or even any entity by `entity_id` to display by stats section: -| Name | Type | Default | Description | -| ----------- | :------: | -------- | ----------------------------------------------- | -| `entity_id` | `string` | Optional | An entity_id with state, i.e. `sensor.vacuum`. | -| `attribute` | `string` | Optional | Attribute name of the stat, i.e. `filter_left`. | -| `unit` | `string` | Optional | Unit of measure, i.e. `hours`. | -| `subtitle` | `string` | Optional | Friendly name of the stat, i.e. `Filter`. | +| Name | Type | Default | Description | +| ---------------- | :------: | -------- | ----------------------------------------------- | +| `entity_id` | `string` | Optional | An entity_id with state, i.e. `sensor.vacuum`. | +| `attribute` | `string` | Optional | Attribute name of the stat, i.e. `filter_left`. | +| `value_template` | `string` | Optional | Jinja2 template returning a value. | +| `unit` | `string` | Optional | Unit of measure, i.e. `hours`. | +| `subtitle` | `string` | Optional | Friendly name of the stat, i.e. `Filter`. | ### `actions` object @@ -154,6 +156,46 @@ You can defined [custom scripts][ha-scripts] for custom actions i.e cleaning spe | `icon` | `string` | Optional | Any icon for action button. | | `service_data` | `object` | `service_data` for `service` call | +## Theming + +This card can be styled by changing the values of these CSS properties (globally or per-card via [`card-mod`][card-mod]): + +| Variable | Default value | Description | +| --------------------------- | ---------------------------------------------------------------- | ------------------------------------ | +| `--vc-background` | `var(--ha-card-background, var(--card-background-color, white))` | Background of the card | +| `--vc-primary-text-color` | `var(--primary-text-color)` | Vacuum name, stats values, etc | +| `--vc-secondary-text-color` | `var(--secondary-text-color)` | Status, stats units and titles, etc | +| `--vc-icon-color` | `var(--secondary-text-color)` | Colors of icons | +| `--vc-toolbar-background` | `var(--vc-background)` | Background of the toolbar | +| `--vc-toolbar-text-color` | `var(--secondary-text-color)` | Color of the toolbar texts | +| `--vc-toolbar-icon-color` | `var(--secondary-text-color)` | Color of the toolbar icons | +| `--vc-divider-color` | `var(--entities-divider-color, var(--divider-color))` | Color of dividers | +| `--vc-spacing` | `10px` | Paddings and margins inside the card | + +### Styling via theme + +Here is an example of customization via theme. Read more in the [Frontend documentation](https://www.home-assistant.io/integrations/frontend/). + +```yaml +my-custom-theme: + vc-background: '#17A8F4' + vc-spacing: 5px +``` + +### Styling via card-mod + +You can use [`card-mod`][card-mod] to customize the card on per-card basis, like this: + +```yaml +type: 'custom:vacuum-card' +style: | + ha-card { + --vc-background: #17A8F4; + --vc-spacing: 5px; + } + ... +``` + ## Animations I've added some animations for this card to make it alive. Animations are applied only for `image` property. Here's how they look like: @@ -199,52 +241,18 @@ This card relies on basic vacuum services, like `pause`, `start`, `stop`, `retur If this card works with your vacuum cleaner, please open a PR and your model to the list. -- Roborock S7 -- Roborock S6 MaxV -- Roborock S6 -- Roborock S6 Pure -- Roborock S5 -- Roborock S5 Max -- Roborock S50 -- Roborock S4 -- Roborock S4 Max -- Roborock E25 -- Roborock E4 -- Mijia Robot Vacuum Cleaner 1C (STYTJ01ZHM) -- Xiaomi Mi Robot (STYJ02YM) -- Xiaomi Mi Robot 1S -- Xiaomi Mi Roborock V1 (SDJQR02RR) -- Xiaomi Mijia 1C -- Roomba 675 -- Roomba 676 -- Roomba 960 -- Roomba 980 -- Roomba 981 -- Roomba i3 -- Roomba i7+ -- Roomba e5 -- Roomba S9 -- Braava M6 -- Roomba s9+ -- Roomba j7 -- Dyson 360 Eye -- Neato D7 -- Neato D6 -- Neato D4 -- Shark IQ -- Ecovacs Deebot 950 -- EcoVacs Deebot OZMO T8 AIVI -- EcoVacs Deebot N79 -- EcoVacs Deebot N8 -- EcoVacs Deebot N8+ -- Eufy Robovac 30c -- Eufy Robovac 15C Max -- Mi Robot Vacuum-Mop P -- EcoVacs T9 AIVI -- Dreame Z10 Pro -- Dreame L10 Pro -- Dreame D9 -- Dreame F9 +- **Roborock** S7, S6 (MaxV, Pure), S5 (Max), S50, S4 (Max), E25, E4 +- **Mijia** Robot Vacuum Cleaner 1C (STYTJ01ZHM) +- **Xiaomi** Mi Robot (STYJ02YM), Mi Robot 1S, Mi Roborock V1 (SDJQR02RR), Mijia 1C, Mi Robot Vacuum-Mop P +- **Roomba** 675, 676, 960980, 981, i3, i7+, e5, S9, s9+, j7 +- **Braava** M6 +- **Dyson** 360 Eye +- **Neato** D7, D6, D4 +- **Shark** IQ +- **Ecova**cs Deebot 950, Deebot OZMO T8 AIVI, Deebot N79, Deebot N8, Deebot N8+, T9 AIVI +- **Eufy** Robovac 30c, Robovac 15C Max +- **EcoVacs** T9 AIVI +- **Dreame** Z10 Pro, L10 Pro, D9, F9 - 360 S7 Pro - [_Your vacuum?_][edit-readme] @@ -284,12 +292,13 @@ MIT © [Denys Dovhan][denysdovhan] [home-assistant]: https://www.home-assistant.io/ [hacs]: https://hacs.xyz -[preview-image]: https://user-images.githubusercontent.com/3459374/83282788-c9e30280-a1e2-11ea-8e13-6208169ddc0a.png +[preview-image]: https://user-images.githubusercontent.com/3459374/164231294-d2c26bbd-ae34-4b41-b909-3a0ae259259e.png [cleaning-gif]: https://user-images.githubusercontent.com/3459374/81119202-fa60b500-8f32-11ea-9b23-325efa93d7ab.gif [returning-gif]: https://user-images.githubusercontent.com/3459374/81119452-765afd00-8f33-11ea-9dc5-9c26ba3f8c45.gif [latest-release]: https://github.com/denysdovhan/vacuum-card/releases/latest [ha-scripts]: https://www.home-assistant.io/docs/scripts/ [edit-readme]: https://github.com/denysdovhan/vacuum-card/edit/master/README.md +[card-mod]: https://github.com/thomasloven/lovelace-card-mod [add-translation]: https://github.com/denysdovhan/vacuum-card/blob/master/CONTRIBUTING.md#how-to-add-translation [macbury-smart-house]: https://macbury.github.io/SmartHouse/HomeAssistant/Vacuum/ [bbbenji-card]: https://gist.github.com/bbbenji/24372e423f8669b2e6713638d8f8ceb2 diff --git a/package-lock.json b/package-lock.json index e9b62865..a3f72fbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "MIT", "dependencies": { "custom-card-helpers": "^1.6.4", - "lit-element": "^2.3.1", + "ha-template": "^1.0.1", + "lit": "^2.0.0", "lodash.get": "^4.4.2" }, "devDependencies": { @@ -493,6 +494,11 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@lit/reactive-element": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.0.tgz", + "integrity": "sha512-0TKSIuJHXNLM0k98fi0AdMIdUoHIYlDHTP+0Vruc2SOs4T6vU1FinXgSvYd8mSrkt+8R+qdRAXvjpqrMXMyBgw==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -1269,6 +1275,11 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", + "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + }, "node_modules/@types/uglify-js": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", @@ -3661,6 +3672,14 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, + "node_modules/ha-template": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ha-template/-/ha-template-1.0.1.tgz", + "integrity": "sha512-Yn/GTXuiiu1g7X/5GDdf3x2uWHNzTkC418mqGmT3e9uGHmPsEsDZJrEQfBWlpWiv7QHfW9ORAhXbi5JyeJQ7oQ==", + "peerDependencies": { + "lit": "^2.0.0" + } + }, "node_modules/handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -4977,6 +4996,16 @@ "node": ">=8" } }, + "node_modules/lit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.0.tgz", + "integrity": "sha512-FDyxUuczo6cJJY/2Bkgfh1872U4ikUvmK1Cb6+lYC1CW+QOo8CaWXCpvPKFzYsz0ojUxoruBLVrECc7VI2f1dQ==", + "dependencies": { + "@lit/reactive-element": "^1.3.0", + "lit-element": "^3.2.0", + "lit-html": "^2.2.0" + } + }, "node_modules/lit-element": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.3.1.tgz", @@ -4990,6 +5019,23 @@ "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.2.1.tgz", "integrity": "sha512-GSJHHXMGLZDzTRq59IUfL9FCdAlGfqNp/dEa7k7aBaaWD+JKaCjsAk9KYm2V12ItonVaYx2dprN66Zdm1AuBTQ==" }, + "node_modules/lit/node_modules/lit-element": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.0.tgz", + "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", + "dependencies": { + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.2.0" + } + }, + "node_modules/lit/node_modules/lit-html": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.0.tgz", + "integrity": "sha512-dJnevgV8VkCuOXLWrjQopDE8nSy8CzipZ/ATfYQv7z7Dct4abblcKecf50gkIScuwCTzKvRLgvTgV0zzagW4gA==", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/loader-utils": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz", @@ -12454,6 +12500,11 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@lit/reactive-element": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.0.tgz", + "integrity": "sha512-0TKSIuJHXNLM0k98fi0AdMIdUoHIYlDHTP+0Vruc2SOs4T6vU1FinXgSvYd8mSrkt+8R+qdRAXvjpqrMXMyBgw==" + }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -13075,6 +13126,11 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "@types/trusted-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", + "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + }, "@types/uglify-js": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", @@ -14901,6 +14957,12 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, + "ha-template": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ha-template/-/ha-template-1.0.1.tgz", + "integrity": "sha512-Yn/GTXuiiu1g7X/5GDdf3x2uWHNzTkC418mqGmT3e9uGHmPsEsDZJrEQfBWlpWiv7QHfW9ORAhXbi5JyeJQ7oQ==", + "requires": {} + }, "handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -15851,6 +15913,35 @@ } } }, + "lit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.2.0.tgz", + "integrity": "sha512-FDyxUuczo6cJJY/2Bkgfh1872U4ikUvmK1Cb6+lYC1CW+QOo8CaWXCpvPKFzYsz0ojUxoruBLVrECc7VI2f1dQ==", + "requires": { + "@lit/reactive-element": "^1.3.0", + "lit-element": "^3.2.0", + "lit-html": "^2.2.0" + }, + "dependencies": { + "lit-element": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.2.0.tgz", + "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", + "requires": { + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.2.0" + } + }, + "lit-html": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.2.0.tgz", + "integrity": "sha512-dJnevgV8VkCuOXLWrjQopDE8nSy8CzipZ/ATfYQv7z7Dct4abblcKecf50gkIScuwCTzKvRLgvTgV0zzagW4gA==", + "requires": { + "@types/trusted-types": "^2.0.2" + } + } + } + }, "lit-element": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.3.1.tgz", diff --git a/package.json b/package.json index 7622de7b..970c4224 100755 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "license": "MIT", "dependencies": { "custom-card-helpers": "^1.6.4", - "lit-element": "^2.3.1", + "ha-template": "^1.0.1", + "lit": "^2.0.0", "lodash.get": "^4.4.2" }, "devDependencies": { @@ -64,6 +65,9 @@ "*.js": "eslint --fix", "*.{js,json,css,yml,md}": "prettier --write" }, + "prettier": { + "singleQuote": true + }, "release": { "plugins": [ "@semantic-release/commit-analyzer", diff --git a/rollup.config.js b/rollup.config.js index 36a31b15..af32bbd2 100755 --- a/rollup.config.js +++ b/rollup.config.js @@ -38,7 +38,14 @@ export default { exclude: 'node_modules/**', }), postcss({ - plugins: [postcssPresetEnv()], + plugins: [ + postcssPresetEnv({ + stage: 1, + features: { + 'nesting-rules': true, + }, + }), + ], extract: false, }), postcssLit({ diff --git a/src/styles.css b/src/styles.css index 5ce16a13..84736f4b 100755 --- a/src/styles.css +++ b/src/styles.css @@ -1,4 +1,17 @@ :host { + --vc-background: var( + --ha-card-background, + var(--card-background-color, white) + ); + --vc-primary-text-color: var(--primary-text-color); + --vc-secondary-text-color: var(--secondary-text-color); + --vc-icon-color: var(--secondary-text-color); + --vc-toolbar-background: var(--vc-background); + --vc-toolbar-text-color: var(--secondary-text-color); + --vc-toolbar-icon-color: var(--secondary-text-color); + --vc-divider-color: var(--entities-divider-color, var(--divider-color)); + --vc-spacing: 10px; + display: flex; flex: 1; flex-direction: column; @@ -8,25 +21,41 @@ ha-card { flex-direction: column; flex: 1; position: relative; - padding: 0px; - border-radius: 4px; + overflow: hidden; } .preview { - background: var(--primary-color); - cursor: pointer; + background: var(--vc-background); + overflow: hidden; position: relative; text-align: center; - border-radius: 4px 4px 0 0; + + &.not-available { + filter: grayscale(1); + } } -.preview.not-available { - filter: grayscale(1); +.header { + display: flex; + justify-content: space-between; +} + +.tips { + display: flex; + gap: var(--vc-spacing); + flex-grow: 1; + flex-wrap: wrap; + padding: var(--vc-spacing); + + & .tip { + cursor: pointer; + } } .map { max-width: 95%; image-rendering: crisp-edges; + cursor: pointer; } @keyframes cleaning { @@ -103,7 +132,9 @@ ha-card { max-width: 90%; max-height: 200px; image-rendering: crisp-edges; - margin: 30px auto 20px auto; + margin: var(--vc-spacing) auto; + cursor: pointer; + filter: brightness(0.9); } .vacuum.on, @@ -131,24 +162,8 @@ ha-card { flex-grow: 1; } -.header { - height: 40px; +.more-info ha-icon { display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - color: var(--text-primary-color); -} - -.battery { - text-align: right; - font-weight: bold; - padding: 8px; -} - -.source { - padding-left: 7px; - text-align: center; } .status { @@ -159,117 +174,100 @@ ha-card { } .status-text { - color: var(--text-primary-color); + color: var(--vc-secondary-text-color); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; - margin-left: calc(20px + 9px); /* size + margin of spinner */ + margin-left: calc(28px + var(--vc-spacing)); /* size + margin of spinner */ } -.status ha-circular-progress { - --mdc-theme-primary: var( - --card-background-color - ); /* hack to override the color */ - min-width: 24px; - width: 24px; - height: 24px; - margin-left: 9px; +.status mwc-circular-progress { + --mdc-theme-primary: var(--vc-secondary-text-color) !important; + margin-left: var(--vc-spacing); } .vacuum-name { text-align: center; font-weight: bold; - color: var(--text-primary-color); + color: var(--vc-primary-text-color); font-size: 16px; } .not-available .offline { text-align: center; - color: var(--text-primary-color); + color: var(--vc-primary-text-color); font-size: 16px; } .metadata { - margin: 10px auto; + margin: var(--vc-spacing) auto; } .stats { - border-top: 1px solid rgba(255, 255, 255, 0.2); + border-top: 1px solid var(--vc-divider-color); display: flex; flex-direction: row; justify-content: space-evenly; - color: var(--text-primary-color); + color: var(--vc-secondary-text-color); } .stats-block { - margin: 10px 0px; + cursor: pointer; + margin: var(--vc-spacing) 0px; text-align: center; - border-right: 1px solid rgba(255, 255, 255, 0.2); + border-right: 1px solid var(--vc-divider-color); flex-grow: 1; -} -.stats-block:last-child { - border: 0px; + &:last-of-type { + border-right: 0px; + } } .stats-value { font-size: 20px; - font-weight: bold; + color: var(--vc-primary-text-color); } ha-icon { - color: #fff; + color: var(--vc-icon-color); } .toolbar { - background: var(--lovelace-background, var(--primary-background-color)); - border-radius: 0 0 4px 4px; + background: var(--vc-toolbar-background); min-height: 30px; display: flex; flex-direction: row; flex-flow: row wrap; flex-wrap: wrap; justify-content: space-evenly; + padding: 5px; + border-top: 1px solid var(--vc-divider-color); } .toolbar ha-icon-button { - color: var(--primary-color); + color: var(--vc-toolbar-text-color); flex-direction: column; width: 44px; height: 44px; --mdc-icon-button-size: 44px; - margin: 5px 0; -} - -.toolbar ha-icon-button:first-child { - margin-left: 5px; -} - -.toolbar ha-icon-button:last-child { - margin-right: 5px; } .toolbar paper-button { - color: var(--primary-color); - flex-direction: column; + color: var(--vc-toolbar-text-color); + display: flex; + align-items: center; margin-right: 10px; padding: 15px 10px; cursor: pointer; -} -.toolbar ha-icon-button:active, -.toolbar paper-button:active { - opacity: 0.4; - background: rgba(0, 0, 0, 0.1); -} - -.toolbar paper-button { - color: var(--primary-color); - flex-direction: row; + & ha-icon { + margin-right: 5px; + color: var(--vc-toolbar-icon-color); + } } .toolbar ha-icon { - color: var(--primary-color); + color: var(--vc-toolbar-icon-color); display: flex; } diff --git a/src/translations/cs.json b/src/translations/cs.json index ddae1612..567a9041 100644 --- a/src/translations/cs.json +++ b/src/translations/cs.json @@ -1,59 +1,59 @@ { - "status": { - "cleaning": "Vysává se", - "auto": "Automatické vysávání", - "spot": "Vysávání na místě", - "edge": "Vysávání při okraji", - "single_room": "Vysávání jedné místnosti", - "paused": "Pozastaveno", - "idle": "Nečinný", - "stop": "Stopped", - "charging": "Nabíjí se", - "returning home": "Vrací se do stanice", - "returning": "Vrací se", - "docked": "Ve stanici", - "unknown": "Neznámý", - "offline": "Vypnuto", - "error": "Chyba" - }, - "source": { - "gentle": "Mírný", - "silent": "Tichý", - "standard": "Standardní", - "medium": "Střední", - "turbo": "Turbo", - "normal": "Normální", - "high": "Vysoký", - "strong": "Silný", - "quiet": "Tichý", - "max": "Max", - "max+": "Max+" - }, - "common": { - "name": "Karta vysavače", - "description": "Karta vysavače vám dovolí ovládat svůj vysavač.", - "start": "Začni vysávat", - "continue": "Pokračuj", - "pause": "Pozastav", - "stop": "Zastav", - "return_to_base": "Vrať se domů", - "locate": "Lokalizuj", - "not_available": "Vysavač není dostupný" - }, - "error": { - "missing_entity": "Je vyžadováno specifikování entity!" - }, - "warning": { - "actions_array": "VAROVÁNÍ: 'actions' jsou rezervovány pro přepsání původních akcí u existujících tlačítek. Pokud jste chtěli měli v plánu přidat další akce, použijte namísto toho možnost 'shortcuts'." - }, - "editor": { - "entity": "Entita (Povinný)", - "map": "Mapa (Nepovinný)", - "image": "Fotka (Nepovinný)", - "compact_view": "Kompaktní zobrazení", - "compact_view_aria_label_on": "Zapni kompaktní zobrazení", - "compact_view_aria_label_off": "Vypni kompaktní zobrazení", - "show_name": "Zobraz název", + "status": { + "cleaning": "Vysává se", + "auto": "Automatické vysávání", + "spot": "Vysávání na místě", + "edge": "Vysávání při okraji", + "single_room": "Vysávání jedné místnosti", + "paused": "Pozastaveno", + "idle": "Nečinný", + "stop": "Stopped", + "charging": "Nabíjí se", + "returning home": "Vrací se do stanice", + "returning": "Vrací se", + "docked": "Ve stanici", + "unknown": "Neznámý", + "offline": "Vypnuto", + "error": "Chyba" + }, + "source": { + "gentle": "Mírný", + "silent": "Tichý", + "standard": "Standardní", + "medium": "Střední", + "turbo": "Turbo", + "normal": "Normální", + "high": "Vysoký", + "strong": "Silný", + "quiet": "Tichý", + "max": "Max", + "max+": "Max+" + }, + "common": { + "name": "Karta vysavače", + "description": "Karta vysavače vám dovolí ovládat svůj vysavač.", + "start": "Začni vysávat", + "continue": "Pokračuj", + "pause": "Pozastav", + "stop": "Zastav", + "return_to_base": "Vrať se domů", + "locate": "Lokalizuj", + "not_available": "Vysavač není dostupný" + }, + "error": { + "missing_entity": "Je vyžadováno specifikování entity!" + }, + "warning": { + "actions_array": "VAROVÁNÍ: 'actions' jsou rezervovány pro přepsání původních akcí u existujících tlačítek. Pokud jste chtěli měli v plánu přidat další akce, použijte namísto toho možnost 'shortcuts'." + }, + "editor": { + "entity": "Entita (Povinný)", + "map": "Mapa (Nepovinný)", + "image": "Fotka (Nepovinný)", + "compact_view": "Kompaktní zobrazení", + "compact_view_aria_label_on": "Zapni kompaktní zobrazení", + "compact_view_aria_label_off": "Vypni kompaktní zobrazení", + "show_name": "Zobraz název", "show_name_aria_label_on": "Zapni zobrazení názvu", "show_name_aria_label_off": "Vypni zobrazení názvu", "show_status": "Zobraz status", @@ -63,5 +63,5 @@ "show_toolbar_aria_label_on": "Zapni zobrazení lišty", "show_toolbar_aria_label_off": "Vypni zobrazení lišty", "code_only_note": "Poznámka: Nastavení akcí a infa je dostupné pouze v editoru kódu." - } } +} diff --git a/src/vacuum-card-editor.js b/src/vacuum-card-editor.js index 15b2f32f..bc70b43f 100644 --- a/src/vacuum-card-editor.js +++ b/src/vacuum-card-editor.js @@ -1,5 +1,4 @@ -import { LitElement, html, css } from 'lit-element'; -import { nothing } from 'lit-html'; +import { LitElement, html, css, nothing } from 'lit'; import { fireEvent } from 'custom-card-helpers'; import localize from './localize'; diff --git a/src/vacuum-card.js b/src/vacuum-card.js index df0d8622..ca07c467 100755 --- a/src/vacuum-card.js +++ b/src/vacuum-card.js @@ -1,12 +1,14 @@ -import { LitElement, html } from 'lit-element'; -import { nothing } from 'lit-html'; +import { LitElement, html, nothing } from 'lit'; import { hasConfigOrEntityChanged, fireEvent } from 'custom-card-helpers'; +import registerTemplates from 'ha-template'; import get from 'lodash.get'; -import './vacuum-card-editor'; import localize from './localize'; import styles from './styles.css'; import defaultImage from './vacuum.svg'; import { version } from '../package.json'; +import './vacuum-card-editor'; + +registerTemplates(); console.info( `%c VACUUM-CARD %c ${version} `, @@ -149,15 +151,15 @@ class VacuumCard extends LitElement { } } - handleMore() { + handleMore(entityId = this.entity.entity_id) { fireEvent( this, 'hass-more-info', { - entityId: this.entity.entity_id, + entityId, }, { - bubbles: true, + bubbles: false, composed: true, } ); @@ -165,76 +167,31 @@ class VacuumCard extends LitElement { handleSpeed(e) { const fan_speed = e.target.getAttribute('value'); - this.callService('set_fan_speed', false, { fan_speed }); - } - - handleStart() { - const actions = this.config.actions; - if (!actions || !actions.start) { - this.callService('start'); - return; - } - - this.callAction(actions.start); + this.callService('set_fan_speed', { isRequest: false }, { fan_speed }); } - handlePause() { - const actions = this.config.actions; - if (!actions || !actions.pause) { - this.callService('pause'); - return; - } - - this.callAction(actions.pause); - } - - handleResume() { - const actions = this.config.actions; - if (!actions || !actions.resume) { - this.callService('start'); - return; - } - - this.callAction(actions.resume); - } + handleAction(action, params = { isRequest: true }) { + const actions = this.config.actions || {}; - handleStop() { - const actions = this.config.actions; - if (!actions || !actions.stop) { - this.callService('stop'); - return; - } - - this.callAction(actions.stop); - } - - handleLocate() { - const actions = this.config.actions; - if (!actions || !actions.locate) { - this.callService('locate', false); - return; - } - - this.callAction(actions.locate); - } - - handleReturnToBase() { - const actions = this.config.actions; - if (!actions || !actions.return_to_base) { - this.callService('return_to_base'); - return; - } + return () => { + if (!actions[action]) { + this.callService(params.defaultService || action, { + isRequest: params.isRequest, + }); + return; + } - this.callAction(actions.return_to_base); + this.callAction(actions[action]); + }; } - callService(service, isRequest = true, options = {}) { + callService(service, params = { isRequest: true }, options = {}) { this.hass.callService('vacuum', service, { entity_id: this.config.entity, ...options, }); - if (isRequest) { + if (params.isRequest) { this.requestInProgress = true; this.requestUpdate(); } @@ -279,24 +236,39 @@ class VacuumCard extends LitElement { const selected = sources.indexOf(source); return html` - - - - - ${localize(`source.${source}`) || source} - - - ${sources.map( - (item, index) => - html` this.handleSpeed(e)} - > - ${localize(`source.${item}`) || item} - ` - )} - +
+ +
+ + + ${localize(`source.${source}`) || source} + +
+ ${sources.map( + (item, index) => + html` + this.handleSpeed(e)} + > + ${localize(`source.${item}`) || item} + + ` + )} +
+
+ `; + } + + renderBattery() { + const { battery_level, battery_icon } = this.getAttributes(this.entity); + + return html` +
+ + ${battery_level}% +
`; } @@ -306,18 +278,26 @@ class VacuumCard extends LitElement { } if (this.map) { - return this.hass.states[this.config.map] && - this.hass.states[this.config.map].attributes.entity_picture - ? html`` + const map = this.hass.states[this.config.map]; + return map && map.attributes.entity_picture + ? html` + this.handleMore(this.config.map)} + /> + ` : nothing; } if (this.image) { - return html``; + return html` + + `; } return nothing; @@ -328,23 +308,33 @@ class VacuumCard extends LitElement { const statsList = stats[state] || stats.default || []; - return statsList.map(({ entity_id, attribute, unit, subtitle }) => { - if (!entity_id && !attribute) { - return nothing; - } - - const value = entity_id - ? this.hass.states[entity_id].state - : get(this.entity.attributes, attribute); + return statsList.map( + ({ entity_id, attribute, value_template, unit, subtitle }) => { + if (!entity_id && !attribute && !value_template) { + return nothing; + } + + const state = entity_id + ? this.hass.states[entity_id].state + : get(this.entity.attributes, attribute); + + const value = html` + + `; - return html` -
- ${value} - ${unit} -
${subtitle}
-
- `; - }); + return html` +
+ ${value} + ${unit} +
${subtitle}
+
+ `; + } + ); } renderName() { @@ -374,10 +364,10 @@ class VacuumCard extends LitElement { ${localizedStatus} - + `; } @@ -396,15 +386,15 @@ class VacuumCard extends LitElement { case 'cleaning': { return html`
- + ${localize('common.pause')} - + ${localize('common.stop')} - + ${localize('common.return_to_base')} @@ -415,11 +405,15 @@ class VacuumCard extends LitElement { case 'paused': { return html`
- + ${localize('common.continue')} - + ${localize('common.return_to_base')} @@ -430,11 +424,15 @@ class VacuumCard extends LitElement { case 'returning': { return html`
- + ${localize('common.continue')} - + ${localize('common.pause')} @@ -451,16 +449,18 @@ class VacuumCard extends LitElement { const execute = () => { this.callAction({ service, service_data }); }; - return html``; + return html` + + + + `; } ); const dockButton = html` `; @@ -469,13 +469,13 @@ class VacuumCard extends LitElement {
@@ -504,19 +504,21 @@ class VacuumCard extends LitElement { } const { state } = this.entity; - const { battery_level, battery_icon } = this.getAttributes(this.entity); return html` -
+
-
- ${this.renderSource()} -
-
- ${battery_level}% - +
+ ${this.renderSource()} ${this.renderBattery()}
+
${this.renderMapOrImage(state)}