Initial commit

This commit is contained in:
Thomas Lovén 2019-06-08 22:08:01 +02:00
commit c84ea8e09c
6 changed files with 586 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
package-lock.json
package.json
webpack.config.js

42
Makefile Normal file
View File

@ -0,0 +1,42 @@
AUTHOR := Thomas Lovén
CARD_TOOLS := $(PWD)/../card-tools
PACKAGE := $(shell basename $(CURDIR))
PACKAGE := $(PACKAGE:lovelace-%=%)
DOCKER_CMD:=docker run --rm -v $(CARD_TOOLS):/card-tools:ro -v $(PWD):/usr/src/$(PACKAGE) -w="/usr/src/$(PACKAGE)" node:11
build: setup
$(DOCKER_CMD) npm run build
dev: setup
$(DOCKER_CMD) npm run watch
setup: package.json package-lock.json webpack.config.js
clean:
rm package.json package-lock.json webpack.config.js
rm -r node_modules
rm $(PACKAGE).js
define WEBPACK_CONFIG
const path = require('path');
module.exports = {
entry: './src/main.js',
mode: 'production',
output: {
filename: '$(PACKAGE).js',
path: path.resolve(__dirname)
}
};
endef
export WEBPACK_CONFIG
webpack.config.js:
echo "$$WEBPACK_CONFIG" >> $@
package-lock.json:
$(DOCKER_CMD) npm install webpack webpack-cli --save-dev
package.json:
$(DOCKER_CMD) /bin/bash -c "npm set init.license 'MIT' && npm set init.author.name '$(AUTHOR)' && npm init -y"
$(DOCKER_CMD) sed -E -i -e '/^ +"main"/d' -e '/^ +"scripts"/a\ "build": "webpack",' -e '/^ +"scripts"/a\ "watch": "webpack --watch --mode=development",' -e '2a\ "private": true,' $@

144
README.md Normal file
View File

@ -0,0 +1,144 @@
card-mod
========
Allows you to add css styles to any lovelace card.
For installation instructions [see this guide](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins).
Install `card-mod.js` as a `module`.
## Usage
This is *not* a new card. Instead it *changes the way pretty much any other card works*.
Specifically, it looks for `style:` in any cards configuration, and applies the [CSS](https://www.w3schools.com/css/) specified there to the card.
The basis of all lovelace cards there's a `ha-card` element, so that's probably where you'd want to start.
---
**Example:**\
Change the text color of an `entities` card to red.
```yaml
type: entities
style: |
ha-card {
color: red;
}
entities:
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
```
![red text](https://user-images.githubusercontent.com/1299821/59151548-38a8d800-8a35-11e9-875a-64e72fd6f5a6.png)
---
By using the element inspector of your browser ([chrome](https://developers.google.com/web/tools/chrome-devtools/inspect-styles/), [firefox](https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Open_the_Inspector), [safari](https://discussions.apple.com/thread/5508711), [explorer](https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/samples/gg589500(v=vs.85))) you can find out how cards are built up and what styles they are using.
**Example** \
Make a `glance` card use smallcaps and change the font size of the title
```yaml
type: entities
style: |
ha-card {
font-variant: small-caps;
}
.card-header {
font-size: 16px;
}
entities:
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
```
![smallcaps](https://user-images.githubusercontent.com/1299821/59151624-9a1d7680-8a36-11e9-9b2d-598c80ff2aa1.png)
You can also use [templates](https://github.com/thomasloven/hass-config/wiki/Mod-plugin-templates) to change the styles dynamically.
**Example** \
Make an `entity-button` card green when the light is on
```yaml
type: entity-button
entity: light.bed_light
style: |
ha-card {
background: [[ if(light.bed_light == "on", "green", "") ]];
}
```
![templates](https://user-images.githubusercontent.com/1299821/59151667-45c6c680-8a37-11e9-819a-ae5a058ac069.png)
Anything you add in `style:` will be put in a `<style>` tag, so you can also use things like css keyframes
**Example** \
Make a blinking button
```yaml
type: entity-button
entity: light.bed_light
style: |
@keyframes blink {
50% {
background: red;
}
}
ha-card {
animation: blink 2s linear infinite;
}
```
![Animated](https://user-images.githubusercontent.com/1299821/59151697-f46b0700-8a37-11e9-842e-a1088be149b4.gif)
## More examples
More examples are available [here](https://github.com/thomasloven/lovelace-card-mod/blob/master/src/example.yaml).
![more](https://user-images.githubusercontent.com/1299821/59151861-9f7cc000-8a3a-11e9-90d2-ff192c4c10a7.gif)
## Advanced usage
When exploring the cards using the element inspector, you might run into something called a `shadow-root` and notice that you can't apply styles to anything inside that.
In this case, you can make `style:` a dictionary instead of a string, where each key is a [`querySelector` string](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) and it's value styles to apply to it - *recursively*. A key of `$` means go into a `shadow-root` and a key of `.` the current element.
This is not for the faint of heart.
**Example**:
Change some things in an `alarm-panel` card.
```yaml
type: alarm-panel
card_icon: mdi:bell
name: Alarm Panel
style:
.: |
ha-card {
--mdc-theme-primary: red;
}
"#keypad mwc-button":
$: |
:host {
background: blue;
}
button {
font-size: 24px !important;
}
"#keypad mwc-button:nth-of-type(12)":
$: |
button {
font-size: 16px !important;
--mdc-theme-primary: green;
}
entity: alarm_control_panel.alarm
```
![advanced](https://user-images.githubusercontent.com/1299821/59151780-59732c80-8a39-11e9-9f19-34d3e0dbd8e9.png)
---
<a href="https://www.buymeacoffee.com/uqD6KHCdJ" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/white_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>

1
card-mod.js Normal file

File diff suppressed because one or more lines are too long

313
src/example.yaml Normal file
View File

@ -0,0 +1,313 @@
title: card-mod
cards:
- type: entities
title: Default
entities:
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
- type: entities
style: |
ha-card {
color: red;
}
entities:
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
- type: glance
title: Glance card
style: |
ha-card {
font-variant: small-caps;
}
.card-header {
font-size: 16px;
}
entities:
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
- type: entity-button
entity: light.bed_light
style: |
ha-card {
background: [[ if(light.bed_light == "on", "green", "") ]];
}
- type: entity-button
entity: light.bed_light
style: |
@keyframes blink {
50% {
background: red;
}
}
ha-card {
animation: blink 2s linear infinite;
}
- type: entities
title: Styled
style: |
ha-card {
background: rgba(0,50,0,0.5);
border-radius: 15px;
}
#states div:nth-of-type(2n+0) {
color: red;
}
entities:
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
- light.bed_light
- type: entities
title: Dynamic styles
style: |
ha-card {
background: url(http://place[[ input_select.background.state ]].com/g/600/400);
--primary-text-color: rgb(250,250,250);
color: [[ if(light.bed_light == "on", "rgb(250,250,250)", "red") ]];
--paper-listbox-background-color: black;
text-shadow: 1px 1px 0 #000;
}
entities:
- input_select.background
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
- type: vertical-stack
cards:
- type: picture-elements
style: |
ha-card {
--top: [[ input_number.y_pos.state ]]%;
--left: [[ input_number.x_pos.state ]]%;
}
title: Dynamic styling of elements
image: "http://placekitten.com/g/800/600"
elements:
- type: custom:hui-state-badge-element
entity: light.bed_light
style:
top: "var(--top)"
left: "var(--left)"
- type: entities
entities:
- input_number.x_pos
- input_number.y_pos
- type: horizontal-stack
cards:
- type: entity-button
entity: light.bed_light
name: blink
style: |
ha-card {
animation: blink 3s linear infinite;
}
@keyframes blink {
50% {
filter: invert(100%);
}
}
- type: entity-button
name: outline
entity: light.bed_light
style: |
ha-card {
outline: 0px none green;
animation: blink 3s linear infinite;
}
@keyframes blink {
24% {
outline: 0px none white;
}
25% {
outline: 0px solid green;
}
50% {
outline: 5px solid green;
}
75% {
outline: 0px solid green;
}
76% {
outline: 0px none white;
}
}
- type: entity-button
name: wiggle
entity: light.bed_light
style: |
ha-card {
animation: wiggle 2s linear infinite alternate;
}
@keyframes wiggle {
0% {
-webkit-transform: rotate(5deg);
transform: rotate(5deg);
}
100% {
-webkit-transform: rotate(-5deg);
transform: rotate(-5deg);
}
}
- type: horizontal-stack
cards:
- type: entity-button
entity: light.bed_light
name: swing
style: |
ha-card {
animation: swing 1s linear infinite alternate;
}
@keyframes swing {
0% {
-webkit-transform: rotate(5deg);
transform: rotate(5deg);
}
100% {
-webkit-transform: rotate(-5deg);
transform: rotate(-5deg);
}
}
- type: entity-button
entity: light.bed_light
name: swell
style: |
ha-card {
animation: pulse 3s linear infinite alternate;
}
@keyframes blinka {
50% { background: white; }
100% { background: blue; }
}
@keyframes pulse {
0% {
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
50% {
-webkit-transform: scale3d(1.15,1.15,1.15);
transform: scale3d(1.15,1.15,1.15);
}
100% {
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
- type: entity-button
entity: light.bed_light
name: flip
style: |
ha-card {
animation: flip 5s linear infinite
}
@keyframes blinka {
50% { background: white; }
100% { background: red; }
}
@keyframes flip {
0% {
-webkit-animation-timing-function: ease-out;
-webkit-transform: perspective(400px) scaleX(1) translateZ(0) rotateY(-1turn);
animation-timing-function: ease-out;
transform: perspective(400px) scaleX(1) translateZ(0) rotateY(-1turn)
}
40% {
-webkit-animation-timing-function: ease-out;
-webkit-transform: perspective(400px) scaleX(1) translateZ(150px) rotateY(-190deg);
animation-timing-function: ease-out;
transform: perspective(400px) scaleX(1) translateZ(150px) rotateY(-190deg)
}
50% {
-webkit-animation-timing-function: ease-in;
-webkit-transform: perspective(400px) scaleX(1) translateZ(150px) rotateY(-170deg);
animation-timing-function: ease-in;
transform: perspective(400px) scaleX(1) translateZ(150px) rotateY(-170deg)
}
80% {
-webkit-animation-timing-function: ease-in;
-webkit-transform: perspective(400px) scale3d(.95,.95,.95) translateZ(0) rotateY(0deg);
animation-timing-function: ease-in;
transform: perspective(400px) scale3d(.95,.95,.95) translateZ(0) rotateY(0deg)
}
to {
-webkit-animation-timing-function: ease-in;
-webkit-transform: perspective(400px) scaleX(1) translateZ(0) rotateY(0deg);
animation-timing-function: ease-in;
transform: perspective(400px) scaleX(1) translateZ(0) rotateY(0deg)
}
}
- type: picture-elements
style: |
@keyframes dvd {
0% {
left: 0%;
top: 50%;
--paper-item-icon-color: red;
}
30% {
left: 75%;
top: 0%;
--paper-item-icon-color: green;
}
50% {
left: 95%;
top: 25%;
--paper-item-icon-color: blue;
}
80% {
left: 25%;
top: 95%;
--paper-item-icon-color: yellow;
}
}
image: "http://placekitten.com/g/800/600"
elements:
- type: state-icon
entity: light.bed_light
tap_action:
action: toggle
style:
animation: dvd 10s linear infinite
left: 0%
top: 50%
- type: alarm-panel
card_icon: mdi:bell
name: Alarm Panel
style:
.: |
ha-card {
--mdc-theme-primary: red;
}
"#keypad mwc-button":
$: |
:host {
background: blue;
}
button {
font-size: 24px !important;
}
"#keypad mwc-button:nth-of-type(12)":
$: |
button {
font-size: 16px !important;
--mdc-theme-primary: green;
}
entity: alarm_control_panel.alarm

82
src/main.js Normal file
View File

@ -0,0 +1,82 @@
import {html, css} from "/card-tools/lit-element.js";
import {fireEvent} from "/card-tools/event.js";
import {parseTemplate} from "/card-tools/templates.js";
const HaCard = customElements.get('ha-card');
const findConfig = function(node) {
if(node.config)
return node.config;
if(node._config)
return node._config;
if(node.host)
return findConfig(node.host);
if(node.parentElement)
return findConfig(node.parentElement);
if(node.parentNode)
return findConfig(node.parentNode);
return null;
};
const applyStyle = function(root, style) {
if(!root || !style) return;
if(typeof style === "string") {
// Remove old styles if we're updating
if(root.querySelector(":scope >.card-mod-style"))
root.removeChild(root.querySelector(":scope > .card-mod-style"));
// Add new style tag to the root element
const styleEl = document.createElement('style');
styleEl.classList = "card-mod-style";
styleEl.innerHTML = parseTemplate(style);
root.appendChild(styleEl);
} else {
Object.keys(style).forEach((k) => {
if(k === ".")
return applyStyle(root, style[k]);
else if(k === "$")
return applyStyle(root.shadowRoot, style[k]);
else {
root.querySelectorAll(`:scope > ${k}`).forEach((el) => {
applyStyle(el, style[k]);
});
return;
}
});
}
}
HaCard.prototype.updated = function(_) {
// Apply styles after updates, if specified
const config = findConfig(this);
if(config && config.style)
applyStyle(this, config.style);
}
HaCard.prototype.firstUpdated = function() {
// Move the header inside the slot instead of in the shadowDOM
// makes it easier to style it consistently
const header = this.shadowRoot.querySelector(".card-header");
if(header)
{
this.insertBefore(header, this.children[0]);
}
// Listen for changes to hass or the location and update
document.querySelector("home-assistant").provideHass(this);
window.addEventListener("location-changed", () => this._requestUpdate());
}
Object.defineProperty(HaCard.prototype, 'hass', {
get() {
return this._hass;
},
set(value) {
if(value !== this._hass) {
const oldval = this._hass;
this._hass = value;
this._requestUpdate('hass', oldval);
}
},
});
fireEvent('ll-rebuild', {});