A different take on designing a Lovelace UI

this is progress, I would not have thought you would still get the error,

can you go to extra_styles.yaml

find this line of code var light_color = theme_var.replace(regex_pattern, calc_lightness);
and replace it with var light_color = 'var(--button-card-light-color)';
add console.log(entity) above it as well if you still get the error.

feel free to undo the changes to light.yaml

Now it look like this

extra_styles:
  extra_styles: >
    [[[
      if (entity) {
          if (entity.entity_id.split('.')[0] === 'light' && variables.state_on) {

              // theme variable and conditions
              let style = getComputedStyle(document.body),
                  theme_var = style.getPropertyValue('--button-card-light-color-temp'),
                  is_hsl = theme_var.startsWith('hsl('),
                  is_color_temp = entity.attributes.color_mode === 'color_temp';

              if (is_hsl && is_color_temp && entity.attributes.brightness) {

                  // calculate lightness in hsl
                  let regex_pattern = /(\d+)(?!.*\d)/g,
                      brightness = entity.attributes.brightness / 2.54,
                      lightness = parseFloat(theme_var.match(regex_pattern)[0]),
                      min = lightness - 10,
                      max = lightness + 10,
                      calc_lightness = brightness * (max - min) / 100 + min;
                  console.log(entity)
                  var light_color = 'var(--button-card-light-color)';
              }
              else {
                  var light_color = 'var(--button-card-light-color)';
              }
          }
      }
      return `

and console

the only thing I can recommend is you go through all the code and add in logging or remove bits until you find where the issue is, then if you can’t fix it come back and someone might help more.

I could keep sending you code to try but its not going to be any faster than you doing the investigation yourself

okay i will try going through the code and add logging - but thank you so must for trying and your time. i really appreciate it

1 Like

hello,

@Mattias_Persson

perhaps someone can help me, i try to make the circle responds to my timer values,

the attribute duration has this value : 05:00:00
the attribute remaining the remaining time, for example 01:00:00

now i want that the duration attribute is the max value of the circle or 100%
i want that if for example 04:00:00 is left from the timer he should show 10%
02:50:00 ===> 50%

and so on, but i stuck with the code, can perhaps someone help?

  [[[ 
                          if (entity) {
                          const timer_runtime = entity.attributes.duration - entity.attributes.remaining;
                          const timer_remaining = entity.attributes.remaining;
                          const radius = 20.5; 
                          const circumference = radius * 2 * Math.PI;
                          return `<svg viewBox="0 0 50 50"><circle cx="25" cy="25" r="${radius}"
                          stroke="#b2b2b2" stroke-width="1" fill="rgba(255,255,255,0.04)"
                          style="transform: rotate(-90deg); transform-origin: 50% 50%;
                          stroke-dasharray: ${circumference};
                          stroke-dashoffset: ${circumference - timer_runtime / 100 * circumference};" />
                          <text x="50%" y="54%" fill="#8d8e90" font-size="14" 
                          text-anchor="middle" alignment-baseline="middle">
                          ${timer_remaining}<tspan font-size="10">%</tspan></text></svg>`;} 
                        ]]]

this will not work, entity.attributes.remaining is not updated while the timer is running, it is only used to restart a pursed timer.

in my timer (see link) I used css to move the entity state to inside my custom circle. while looking into this I did see an example of adding a new card inside the circle. see this post Lovelace: Button card - #6274 by florian.ec

this thread has some examples or creating automations and templates to track time remaining in seconds for a timer

Ok :sunglasses:

  base_1:
    template:
      - settings
      - tilt
      - extra_styles
    variables:
      state_on: >
        [[[ return ['on', 'home', 'cool', 'fan_only', 'playing', 'unlocked'].indexOf(!entity || entity.state) !== -1; ]]]
      state: >
        [[[ return !entity || entity.state; ]]]
      entity_id: >
        [[[ return !entity || entity.entity_id; ]]]
      entity_picture: >
        [[[ return !entity || entity.attributes.entity_picture; ]]]
      timeout: >
        [[[ return !entity || Date.now() - Date.parse(entity.last_changed); ]]]
    aspect_ratio: 1/1
    show_state: true
    show_icon: false
    styles:
      grid:
        - grid-template-areas: |
            "icon  circle"
            "n     n"
            "s     s"
        - grid-template-columns: repeat(2, 1fr)
        - grid-template-rows: auto repeat(2, min-content)
        - gap: 1.3%
        - align-items: start
      name:
        - justify-self: start
        - line-height: 121%
      state:
        - justify-self: start
        - line-height: 115%
      card:
        - border-radius: var(--button-card-border-radius)
        - border-width: 0
        - -webkit-tap-highlight-color: rgba(0,0,0,0)
        - transition: none
        - --mdc-ripple-color: >
            [[[
              return variables.state_on
                  ? 'rgb(0, 0, 0)'
                  : '#97989c';
            ]]]
        - color: >
            [[[
              return variables.state_on
                  ? '#4b5254'
                  : '#97989c';
            ]]]
        - background-color: >
            [[[
              return variables.state_on
                  ? 'rgba(255, 255, 255, 0.85)'
                  : 'rgba(115, 115, 115, 0.25)';
            ]]]

  icon_energy_2:
    custom_fields:
      icon: >
        <svg viewBox="0 0 472 472">
          <path d="M469.335,273.067h-17.067V256h17.067c4.233,0,7.825-3.106,8.44-7.296c0.623-4.19-1.929-8.192-5.99-9.412l-164.582-49.374
            v-84.582l103.996,31.198h-86.929c-4.719,0-8.533,3.823-8.533,8.533c0,4.71,3.814,8.533,8.533,8.533h110.933v17.067h-17.067
            c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533h51.2c4.719,0,8.533-3.823,8.533-8.533s-3.814-8.533-8.533-8.533
            h-17.067V153.6h17.067c4.233,0,7.825-3.106,8.44-7.296c0.623-4.19-1.929-8.192-5.99-9.412l-167.1-50.133L264.373,4.77
            c-1.425-2.901-4.369-4.753-7.603-4.77h-0.06c-3.209,0-6.153,1.801-7.612,4.668l-43.375,85.333
            c-0.043,0.094-0.026,0.188-0.068,0.282c-0.247,0.512-0.316,1.075-0.461,1.63c-0.137,0.555-0.316,1.101-0.333,1.664
            c0,0.102-0.06,0.188-0.06,0.29v42.667H101.923l88.218-25.95c4.523-1.323,7.108-6.067,5.777-10.59
            c-1.323-4.514-6.042-7.117-10.59-5.786L40.262,136.875c-4.07,1.203-6.647,5.205-6.042,9.412c0.606,4.198,4.207,7.313,8.448,7.313
            h17.067v17.067H42.668c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533h51.2c4.719,0,8.533-3.823,8.533-8.533
            s-3.814-8.533-8.533-8.533H76.801V153.6h128v85.333H101.923l88.218-25.95c4.523-1.323,7.108-6.067,5.777-10.59
            c-1.323-4.514-6.042-7.108-10.59-5.786L40.262,239.275c-4.07,1.203-6.647,5.205-6.042,9.412c0.606,4.198,4.207,7.313,8.448,7.313
            h17.067v17.067H42.668c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533h51.2c4.719,0,8.533-3.823,8.533-8.533
            s-3.814-8.533-8.533-8.533H76.801V256H202.02l-63.394,221.867h-27.691c-4.719,0-8.533,3.823-8.533,8.533v17.067
            c0,4.71,3.814,8.533,8.533,8.533s8.533-3.823,8.533-8.533v-8.533h25.583h0.026h221.824h0.026h25.609v8.533
            c0,4.71,3.814,8.533,8.533,8.533s8.533-3.823,8.533-8.533V486.4c0-4.71-3.814-8.533-8.533-8.533h-27.691l-66.176-231.595v-38.537
            l103.996,31.198h-86.929c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533h110.933v17.067h-17.067
            c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533h51.2c4.719,0,8.533-3.823,8.533-8.533
            S474.054,273.067,469.335,273.067z M156.383,477.867l27.887-97.587l55.842,35.524l-59.52,37.879
            c-3.977,2.526-5.146,7.808-2.62,11.785c1.63,2.551,4.386,3.951,7.211,3.951c1.57,0,3.149-0.427,4.574-1.34l66.244-42.155
            l81.621,51.942H156.383z M352.556,467.132L189.16,363.153l19.43-68.019l31.932,21.086l-31.889,21.052
            c-3.934,2.594-5.018,7.893-2.415,11.827c1.63,2.483,4.352,3.823,7.125,3.823c1.613,0,3.251-0.452,4.693-1.408l37.965-25.071
            l61.116,40.354l-40.277,25.634c-3.968,2.534-5.146,7.799-2.611,11.784c1.621,2.552,4.386,3.951,7.211,3.951
            c1.562,0,3.149-0.435,4.574-1.34l41.719-26.547L352.556,467.132z M318.261,347.102l-57.515-37.973
            c-0.009-0.009-0.017-0.017-0.034-0.026l-47.198-31.172L219.777,256h53.291c4.719,0,8.533-3.823,8.533-8.533
            s-3.814-8.533-8.533-8.533h-51.2V204.8h51.2c4.719,0,8.533-3.823,8.533-8.533s-3.814-8.533-8.533-8.533h-51.2V153.6h51.2
            c4.719,0,8.533-3.823,8.533-8.533c0-4.71-3.814-8.533-8.533-8.533h-51.2V102.4h42.667c4.719,0,8.533-3.823,8.533-8.533
            c0-4.71-3.814-8.533-8.533-8.533h-37.291l29.338-57.719l33.553,68.241v151.612c0,0.794,0.111,1.579,0.324,2.347l8.038,28.117
            l-21.299,14.063c-3.934,2.594-5.009,7.893-2.415,11.819c1.638,2.492,4.361,3.831,7.125,3.831c1.621,0,3.251-0.452,4.693-1.408
            l16.811-11.102L318.261,347.102z"/>
        </svg>

  icon_forno_3:
    styles:
#      icon:
#        - width: 70%
      custom_fields:
        icon:
          - width: 88%
          - margin-left: -14%
          - margin-top: -1%
    custom_fields:
      icon: >
        <svg viewBox="0 0 51 62.75">
          <path d="M3.5,50.5c0,0.5499878,0.4500122,1,1,1h46c0.5499878,0,1-0.4500122,1-1V17.5004883h-48V50.5z M45.5,46.5h-36      c-0.5499878,0-1-0.4500122-1-1V31.5004883h38V45.5C46.5,46.0499878,46.0499878,46.5,45.5,46.5z M8.5,29.5004883v-3h38v3H8.5z       M9.5,20.5h36c0.5499878,0,1,0.4500122,1,1v3.0004883h-38V21.5C8.5,20.9500122,8.9500122,20.5,9.5,20.5z"/>
          <path d="M50.5,3.5h-46c-0.5499878,0-1,0.4500122-1,1v11.0004883h48V4.5C51.5,3.9500122,51.0499878,3.5,50.5,3.5z M12.5,12.5      c-1.0999756,0-2-0.9000244-2-2s0.9000244-2,2-2c1.1000366,0,2,0.9000244,2,2S13.6000366,12.5,12.5,12.5z M34.5,12.5      c0,0.5499878-0.4500122,1-1,1h-12c-0.5499878,0-1-0.4500122-1-1v-4c0-0.5499878,0.4500122-1,1-1h12c0.5499878,0,1,0.4500122,1,1      V12.5z M42.5,12.5c-1.0999756,0-2-0.9000244-2-2s0.9000244-2,2-2c1.1000366,0,2,0.9000244,2,2S43.6000366,12.5,42.5,12.5z"/>
        </svg>

Hello,

Thanks for your code, but I still have two problems:

ok i tried your code or part of your code, the circle works, now i have the problem that when the timer is off you can still see the circle, how can i adjust that?

timer_on

timer_off

i tried it with

stroke: >
                                            [[[
                                               if (entity) {
                                                    return entity.state === 'active' ?
                                                      #8D8E90 :
                                                      red;
                                                    }
                                             
                                            ]]] 

but no luck.

and if I now want to display the attribute “finish_at” of the entity within the circle, for example, how do I have to adapt the code?

here is the complete code

- type: custom:button-card
                    entity: timer.test
                    name: Weihnachtsbeleuchtung
                    tap_action:
                      !include popup/christmas_lights.yaml
                    template:
                      - base
                      - icon_christmas
                    
                    custom_fields:
                      countdown: |
                            [[[
                                  setTimeout(() => {

                                    let elt = this.shadowRoot,
                                    circle_stroke = elt.getElementById('circle_stroke'),
                                    r = 9,
                                    c = r * 2 * Math.PI,
                                    now = new Date().getTime(),
                                    endDate = new Date(entity.attributes.finishes_at),
                                    remaining = entity.attributes.remaining.split(':'),
                                    duration = entity.attributes.duration.split(':'),
                                    startDate = new Date(endDate.getTime() - (remaining[0]*3600+remaining[1]*60)*1000),
                                    percent = ((now - startDate) / (endDate - startDate)) * 100;
                                    circle_stroke.style.strokeDashoffset = c - percent / 100  * c;
                                    circle_stroke.style.strokeWidth = 1;
                                  }, 0);

                                  let r = 9,
                                  c = r * 2 * Math.PI,
                                  state = true,
                                  input = 12;

                                  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: 1.5;
                                        
                                      }
                                      #circle_stroke{
                                        stroke: #8D8E90;
                                        fill: none;
                                      }
                                      #circle_bg {
                                        stroke: none;
                                        fill: none;

                                      }
                                     
                                      tspan {
                                        font-size: 10.5px;
                                      }
                                      #circle_value, tspan {
                                        text-anchor: middle;
                                        dominant-baseline: central;
                                      }
                                    </style>
                                    <circle id="circle_stroke" cx="25" cy="25" r="${r}"/>
                                    <circle id="circle_bg" cx="25" cy="25" r="${r}"/>
                                   
                                  </svg>       `;           
                            ]]]
                
                    styles:
                   
                     custom_fields:
                        countdown:
                          - position: absolute
                          - width: 100%
                          - height: 100%
                          - margin: -28% -30% 0 0
                          - display: initial
                          - opacity: 1
                          - justify-self: end  

my code is just adapted from the circle template code use that as a reference.
to add text you need to add <text id="circle_value" x="50%" y="52%">${input_text}</tspan></text>
under <circle id="circle_bg" cx="25" cy="25" r="${r}"/>
and replace input_text with the value you would like to have there.

as for it displaying while off I have hard coded state to true in my example, try replacing state = true with state = variables.state_on

the problem with the endtime is solved, but the problem with the visible circle when timer is off is not solved, its still visible with state = variables.state_on

timer_on

timer_off

my code

- type: custom:button-card
                    entity: timer.test
                    name: Weihnachtsbeleuchtung
                    tap_action:
                      !include popup/christmas_lights.yaml
                    template:
                      - base
                      - icon_christmas
                    
                    custom_fields:
                      countdown: |
                            [[[
                                  setTimeout(() => {

                                    let elt = this.shadowRoot,
                                    circle_stroke = elt.getElementById('circle_stroke'),
                                    r = 9,
                                    c = r * 2 * Math.PI,
                                    now = new Date().getTime(),
                                    endDate = new Date(entity.attributes.finishes_at),
                                    remaining = entity.attributes.remaining.split(':'),
                                    duration = entity.attributes.duration.split(':'),
                                    startDate = new Date(endDate.getTime() - (remaining[0]*3600+remaining[1]*60)*1000),
                                    percent = ((now - startDate) / (endDate - startDate)) * 100;
                                    circle_stroke.style.strokeDashoffset = c - percent / 100  * c;
                                    circle_stroke.style.strokeWidth = 1;
                                  }, 0);

                                  let r = 9,
                                  c = r * 2 * Math.PI,
                                  state = variables.state_on,
                                  input = 12,
                                  
                                  input_text = new Date(entity.attributes.finishes_at),
                                  hours = input_text.getHours(),
                                  minutes = input_text.getMinutes();


                                  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: 1.5;
                                        
                                      }
                                      #circle_stroke{
                                        stroke: #8D8E90;
                                        fill: none;
                                      }
                                      #circle_bg {
                                        stroke: none;
                                        fill: none;
                                      }
                                      text {
                                        font-size: 6px;
                                        font-weight: 40;
                                        letter-spacing: -0.02rem;
                                        fill: #97989c;
                                      }
                                      tspan {
                                        font-size: 10.5px;
                                      }
                                      #circle_value, tspan {
                                        text-anchor: middle;
                                        dominant-baseline: central;
                                      }
                                    </style>
                                    
                                    <circle id="circle_stroke" cx="25" cy="25" r="${r}"/>
                                    <circle id="circle_bg" cx="25" cy="25" r="${r}"/>
                                    <text id="circle_value" x="50%" y="52%">${hours}:${minutes}</tspan></text>
                                  </svg>       `;           
                            ]]]
                
                    styles:
                     custom_fields:
                        countdown:
                          - position: absolute
                          - width: 100%
                          - height: 100%
                          - margin: -28% -30% 0 0
                          - display: initial
                          - opacity: 1
                          - justify-self: end   

in your code, you doesnt use the state = variables.state_on or overseen i something?

that is correct, I use a condition to hid the full card when the timer is not active.

as I said my code is based on the circle template, and you should look at that.

depending on what you are trying to do there are lots of options

this ternary statement should remove the circle completely when the timer is off (!state)
replace return with
return return !state ? '' :

the circle template toggles the CSS for the fill and stroke of the line depending on the state like this

stroke: ${state ? 'var(--c-stroke-color-on)' : 'var(--c-stroke-color-off)'};
fill: ${state ? 'var(--c-fill-color-on)' : 'var(--c-fill-color-off)'};
1 Like

yes that works well, thank you very much :grinning:

timer_works

2 Likes

Hi. I have problems with my mobile view.
Tried different things, but it won’t work.
I mean the space/margin between the rows.
Does anyone have an idea?

Thanks
Sascha

Is the theme on for that device?
can you provide the yaml for the dashboard?
can you provide more info on “Tried different things”, what are the things you have tried?

Hi everyone - I’m an extreme noob when it comes to yaml. :slightly_smiling_face: Hoping someone can help me out with what I am sure is a super dumb question.

I’m just starting to try to implement this UI and am running into this issue with every card:

Because I have multiple yaml dashboards going, I’ve had to edit my configuration.yaml slightly, but everything else is largely the same save for some entity/name references. It seems like I’m just not referencing the “extra_styles.yaml” somehow? I’m trying to keep my config organized, so it’s currently in config/ui-lovelace/button_card_templates with the rest of the light.yaml, icon_hue.yaml, etc. And for reference, this is the button_card code:

      - type: grid
        title: Kitchen
        view_layout:
          grid-area: kitchen
        columns: 2
        cards:

          - type: custom:button-card
            entity: light.kitchen_cabinet_sink_lights_2
            name: Cabinet & Sink Lights
            template:
              - light
              - icon_hue

         - type: custom:button-card
            entity: light.laundry_room_cabinet_lights
            name: Laundry Lights
            template:
              - light
              - icon_shade

What am I doing wrong? Any help would be appreciated!

hi Amanda, welcome, and don’t worry we are all noobs at some point.

can I confirm a few things?
do you have this line at the top of your dashboard yaml?

button_card_templates:
 !include_dir_merge_named button_card_templates

button_card_templates is a relative path to the folder that contains all the templates.
so as you have moved the folder you may need to change this line depending on the location of your dashboard yaml

it also looks like you need to set up the theme, have you done this?

thank you for providing a good questions. :star:

1 Like

Im updating my version of this UI from pre browser mod 2, i believe i have fixed all the breaking changes apart from this one:

it seems like the “placeholder grid areas” are having borders generated, i tried applying the fix mentioned here: HA 2022.11 introduces border to cards - theme update required to resolve. · Issue #118 · matt8707/hass-config · GitHub

but it seems to have not fixed the issue. Does anyone have any suggestions? I can post snippets if you need them, i don’t know where to look to resolve this as other who had the border issue appeared to not have it specifically here. will post snippets of my work if someone can point me in the right direction, cheers,

post ui-lovelace.yaml and I’ll have a look

Hi Mason,

Do you have an idea how I can add a sound? So as soon as the timer ends, there should be a sound.

Use a normal home assistant automation, the timer finishing will send out an event that an automation can listen for. I have it sending a notification to my tv and my phone for now but I have plans to expand the automation. You could send a service call to the tablet and play a sound if your tablet supports that.

1 Like