A different take on designing a Lovelace UI

i tired this code on another button which has different charged states for my car but its permently on (lit)

i changed the state of 50 to “completed”

image

          - type: custom:button-card
            entity: sensor.myenergi_zappi_20197616_status
            name: <marquee behavior=scroll scrollamount="3">Zappi Status</marquee>
            variables:
              state_on: >
                [[[ return !entity || entity.state || Completed; ]]]
            custom_fields:
               icon: >
                 <ha-icon icon="mdi:car-electric"></ha-icon>
            template:
              - base  

this line is wrong

[[[ return !entity || entity.state || Completed; ]]]

you have said, be on when you don’t have an entity OR the entity has a state OR Completed.

this section is not valid code || Completed; I think you want something like

                [[[ return !entity || entity.state === "Completed"; ]]]

OR

state_on: >
      [[[ return ['Completed'].indexOf(!entity || entity.state) !== -1; ]]]

as I don’t know what the state of your entity is I can’t help more than that

thank you very much let me try these

i think there are 3 states (paused, completed, charging)

Not sure if this topic is only related to the sidebar card, but with the use of “standard” tools like conditional, layout card browser mod and kiosk, and the initial Idea of someone else, this is where I am at the moment. A little bit of a different take…


4 Likes

wow, looks pretty nice!
how did you implement the sankey diagram? and you mind sharing the name of the fonts?

Sankey chart: Anyone using the Sankey Chart Card?
Font: a Formula1 font found on the web

3 Likes

Looks reallygood - do you share your config anywhere?

Can someone please help me add under the code of Sidebar.yaml Image, in my case QRCODE.

Thanks

Srry, not yet, still optimizing. Might in the future

1 Like

To get more entities on the dashboard, i nested multiple grid-cards inside a swipe-card so that i can swipe though the grid-cards (same principle as the conditional media area).

The problem i have now is, that i’m no longer able to adjust the brightness by dragging the circle. Does anyone know how to get around this? My skills are not good enough to figure it out.

Thanks!

Edit: It seems to work fine on my android phone, where the tilt option is disabled.

screen-capture_Ek6lqrkw

This is the lovelace yaml i’m using:

- type: grid
    title: Stue
    view_layout:
      grid-area: vardagsrum
    columns: 1
    cards:
      - type: custom:swipe-card
        parameters:
          speed: 550
          spaceBetween: 40
          threshold: 5
        cards:
          - type: grid
            columns: 2
            cards:
              - type: custom:button-card
                entity: light.stue_skaenk_1
                name: Skænk
                template:
                  - light
                  - icon_lamp
              - type: custom:button-card
                entity: light.stue_gulvlampe_1_sofa
                name: Gulvlampe
                template:
                  - light
                  - icon_shade
              - type: custom:button-card
                entity: media_player.samsung_tv
                name: Tv
                state_display: >
                  [[[
                    if (variables.state === 'on') {
                        return 'Tændt';
                    }
                    if (variables.state === true) {
                        return variables.translate_unknown;
                    }
                  ]]]
                double_tap_action: !include popup/stue_tv.yaml
                template:
                  - base
                  - icon_tv
                  #- icon_ambilight
                  - loader
              - type: custom:button-card
                entity: light.stue_lille_lampe_sofa
                name: Lille lampe
                #double_tap_action: !include popup/vardagsrum_balkong.yaml
                template:
                  - light
                  - icon_hue
                  - loader
          - type: grid
            columns: 2
            cards:
              - type: custom:button-card
                entity: climate.stue
                name: Radiator
                double_tap_action: !include popup/stue_klima.yaml
                template:
                  - base
                  - icon_climate
                  - circle
                variables:
                  circle_input: >
                    [[[
                      if (entity) {
                          return entity.state === 'cool'
                              ? Math.round(entity.attributes.temperature).toString()
                              : Math.round(entity.attributes.current_temperature).toString();
                      }
                    ]]]
                  circle_input_unit: "°C"
    
              - type: custom:button-card
                entity: light.stue_tvbord_1
                name: TV bord
                template:
                  - light
                  - icon_lamp
    
              - type: custom:button-card
                entity: light.skabslys_spisestue
                name: Vitrine
                template:
                  - light
                  - icon_wled

And the circle template:

circle:
  styles:
    card:
      - --c-stroke-color-on: "#b0b0b0"
      - --c-stroke-color-off: none
      - --c-fill-color-on: none
      - --c-fill-color-off: rgba(255,255,255,0.04)
      - --c-stroke-width: 2.3
      - --c-stroke-width-dragging: 4
      - --c-font-color: "#97989c"
      - --c-font-size: 14px
      - --c-unit-font-size: 10.5px
      - --c-font-weight: 700
      - --c-letter-spacing: -0.02rem
    custom_fields:
      circle:
        - display: initial
        - width: 88%
        - margin: -3% 2% 0 0
        - justify-self: end
        - opacity: 1
  custom_fields:
    circle: >
      [[[
        if (entity) {
            let r = 22.1,
                c = r * 2 * Math.PI,
                tspan = '<tspan dx=".2" dy="-.4">',
                domain = entity.entity_id.split('.')[0],
                state = variables.state_on,
                input = variables.circle_input || ' ',
                unit = variables.circle_input_unit || ' ';

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *             CIRCLE              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            let circle = (state, input, unit) => {
                return `
                  <svg viewBox="0 0 50 50">
                    <style>
                      circle {
                        transform: rotate(-90deg);
                        transform-origin: 50% 50%;
                        stroke-dasharray: ${c};
                        stroke-dashoffset: ${typeof input === 'number' && c - input / 100 * c};
                        stroke-width: var(--c-stroke-width);
                        stroke: ${state ? 'var(--c-stroke-color-on)' : 'var(--c-stroke-color-off)'};
                        fill: ${state ? 'var(--c-fill-color-on)' : 'var(--c-fill-color-off)'};
                      }
                      text {
                        font-size: var(--c-font-size);
                        font-weight: var(--c-font-weight);
                        letter-spacing: var(--c-letter-spacing);
                        fill: var(--c-font-color);
                      }
                      tspan {
                        font-size: var(--c-unit-font-size);
                      }
                      #circle_value, tspan {
                        text-anchor: middle;
                        dominant-baseline: central;
                      }
                    </style>
                    <circle id="circle_stroke" cx="25" cy="25" r="${r}"/>
                    <text id="circle_value" x="50%" y="52%">${input}${tspan}${unit}</tspan></text>
                  </svg>

                  ${domain === 'light' && `
                      <input id="circle_slider" type="range" min="0" max="100" value="${input}">
                  `}
                `;
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *              LIGHT              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            if (domain === 'light' && state) {

                // wait 0ms for shadow dom
                setTimeout(() => {

                    // then get elements
                    let elt = this.shadowRoot,
                        circle_slider = elt.getElementById('circle_slider'),
                        circle_value = elt.getElementById('circle_value'),
                        circle_stroke = elt.getElementById('circle_stroke');

                    // approximate position of thumb relative to circle
                    circle_slider.style.top = `${(circle_slider.value - 50) / 1.66 - 1}%`;

                    // debug position
                    let debug = false;
                    if (debug) circle_slider.style.opacity = 0.3;

                    // pass each event to handler
                    ['click', 'input', 'mousedown', 'mouseup', 'touchstart', 'touchend'].forEach((event) => {
                        circle_slider.addEventListener(event, handler, { passive: true })
                    });

                    function handler(event) {

                        // "this" refers to slider
                        if (event.target === this) {

                            // bypass button-card tap_action
                            event.stopPropagation();

                            // update circle_value
                            circle_value.innerHTML = `${this.value}${tspan}${unit}</tspan>`;

                            // update stroke
                            circle_stroke.style.strokeDashoffset = c - this.value / 100 * c;
                            circle_stroke.style.strokeWidth = 'var(--c-stroke-width-dragging)';
                            
                            // set cursor while dragging
                            if (event.type === 'mousedown' || event.type === 'input') {
                                this.style.cursor = 'grabbing';
                            } else {
                                this.style.cursor = 'grab';
                            }

                            // reset stroke width if value doesn't change
                            if (input == this.value && (event.type === 'click' || event.type === 'touchend'))
                                circle_stroke.style.strokeWidth = 'var(--c-stroke-width)';

                            // on release
                            if (event.type === 'mouseup' || event.type === 'touchend') {

                                // display loader if brightness is 0
                                if (circle_slider.value == 0 && elt.getElementById('loader')) {
                                    elt.getElementById('loader').style.display = 'initial';
                                    elt.getElementById('circle').style.display = 'none';
                                }

                                // set brightness
                                hass.callService('light', 'turn_on', {
                                    entity_id: entity.entity_id,
                                    brightness_pct: this.value
                                });
                            }
                        }
                    }
                }, 0);

                return circle(state, input, unit);
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *             PERSON              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            else if (domain === 'person') {
                let time = c => {
                    let s = (c/1e3),
                        m = (c/6e4),
                        h = (c/36e5),
                        d = (c/864e5);
                    return s < 60
                        ? parseInt(s) + 's'
                        : m < 60 ? parseInt(m) + 'm'
                        : h < 24 ? parseInt(h) + 'h'
                        : parseInt(d) + 'd';
                };
                let input = states[variables.retain] === undefined || states[variables.retain].state === 'unavailable'
                        ? time(Date.now() - Date.parse(entity.last_changed))
                        : time(Date.now() - Date.parse(states[variables.retain].state)),
                    unit = ' ';
                return circle(state, input, unit);
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *             CLIMATE             *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            else if (domain === 'climate') {
                return circle(state, input, unit);
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *              OTHER              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            else if (variables.state_on) {
                return circle(state, input, unit);
            }
        }
      ]]]
1 Like

I’m a developer and this thing had me stuck for a bit swell, you need to add touchStartPreventDefault: false to the parameters section of the swipe-card

- type: custom:swipe-card
  start_card: 1
  parameters:
    touchStartPreventDefault: false

this is my full config fro my swipe cards

- type: custom:swipe-card
  start_card: 1
  parameters:
    touchStartPreventDefault: false
    roundLengths: true
    effect: coverflow
    speed: 650
    spaceBetween: 20
    threshold: 7
    coverflowEffect:
      rotate: 80
      depth: 300

BTW this is a perfect example of how to ask a good question.

4 Likes

Thank you very much! I have battled this for days now :grinning_face_with_smiling_eyes:

1 Like

happy to help, would love to see your full dashboard once its complete

Sure, i’ll post an update when I have something worth sharing.

are you able to share your layout card config please? I’m interested in how you’ve done your sidebar and top menu

i have this already in my base tempate of button-card-templates

        - background-color: >
            [[[
              return variables.state_on
                ? 'rgba(255, 255, 255, 0.8)'
                : 'rgba(115, 115, 115, 0.2)';
            ]]]

Which turns the background on to white when a device is on.

I’m looking to have the background colour change with different power values

So for example if power 50w (white) if 100w (yellow) if 200w (red)

how can i do this?

@tomcoleman try this, this gives me the following. You only need to change let power = "251"; to your own sensor. something like let power = states['sensor.YOURSENSOR'].state

            styles:
              card:
                - background-color: >
                    [[[
                      let power = "251";
                      if ((power >= 50) && (power < 100))
                        return '#FFFFFF';
                        if ((power >= 100) && (power < 200))
                          return '#0099cc';
                            if (power >= 200)
                              return '#ff0000';
                      else 
                        return "rgba(115, 115, 115, 0.25)";
                    ]]]

test|690x160

Maybe not the cleanest code, but i don’t have enough javascript knowledge to make it cleaner.

thanks i have set it to

          - type: custom:button-card
            name: <marquee behavior=scroll scrollamount="3">Kitchen PV</marquee>
            entity: sensor.solax_pv_power_2
            template:
              - base
            styles:
              card:
                - background-color: >
                    [[[
                      let power = "sensor.solax_pv_power_2";
                      if ((power >= 50) && (power < 100))
                        return '#FFFFFF';
                        if ((power >= 100) && (power < 200))
                          return '#0099cc';
                            if (power >= 200)
                              return '#ff0000';
                      else 
                        return "rgba(115, 115, 115, 0.25)";
                    ]]]
            custom_fields:
               icon: >
                 <ha-icon icon="mdi:solar-power-variant"></ha-icon> 

and it shows no change

image

got it

its now showing

           styles:
              card:
                - background-color: >
                    [[[
                      let power = states['sensor.solax_pv_power_2'].state
                      if ((power >= 50) && (power < 100))
                        return '#FFFFFF';
                        if ((power >= 100) && (power < 200))
                          return '#0099cc';
                            if (power >= 200)
                              return '#ff0000';
                      else 
                        return "rgba(115, 115, 115, 0.25)";
                    ]]]
            custom_fields:
               icon: >
                 <ha-icon icon="mdi:solar-power-variant"></ha-icon> 

image

although now cant see text clearly ! LOL

@tomcoleman You can do the same with color to change your text color.