A different take on designing a Lovelace UI

Yes excatly what i did, even restarted just to be sure

ok must be something else, as far as I can tell your template is correct, your usage is fine and your config is also fine.

do any popups work
you said

I assume that you are referring to the mushroom cards work in the ui builder.

Can you show me the state of light.livingroom is that a single list or a light group?
if its a group can you run this in the template tab of the developer tools

{%- for entity_id in states.light.kitchen_lights.attributes.entity_id -%}
  {{entity_id}} is {{states(entity_id)}}
  {{- '\n' -}}
{%- endfor %}

Yes it was the ui builder mushroom card i reffered to - any of the browser mod pop up work, my climate, computer etc. but with hold action

here is the light livingroom - its not a group but a single light

try this, i’m honestly showing in the dark at this point.

light:
  template:
    - base
    - loader
  double_tap_action:
    action: fire-dom-event
    browser_mod:
      service: browser_mod.popup
      data:
        title: >
          [[[
            return !entity || entity.attributes.friendly_name;
          ]]]
        content:
          type: entities
          card_mod:
            style: |
              #states {
                padding-top: 0.5em;
              }
          entities: >
            [[[
              if (entity) {
                  let lights = [],
                      id = Boolean(entity.attributes.entity_id)
                          ? [entity.entity_id].concat(entity.attributes.entity_id)
                          : [entity.entity_id];

                  for (let i = 0; i < id.length; i++) {
                      lights.push({
                          "type": "custom:mushroom-light-card",
                          "entity": id[i],
                          "fill_container": false,
                          "primary_info": "name",
                          "secondary_info": "state",
                          "icon_type": "icon",
                          "show_brightness_control": true,
                          "use_light_color": true,
                          "show_color_temp_control": true,
                          "show_color_control": true,
                          "collapsible_controls": true
                      });
                  }
                  return lights;
              }
            ]]]

didn’t change anything, just that the circle is missing now. - yeah i have also been struggle with this for a month now haha, but i appriciate the help

try this,

light:
  template:
    - base
    - circle
    - loader
  double_tap_action:
    action: fire-dom-event
    browser_mod:
      service: browser_mod.popup
      data:
        title: >
          [[[
            return !entity || entity.attributes.friendly_name;
          ]]]
        content:
          type: entities
          card_mod:
            style: |
              #states {
                padding-top: 0.5em;
              }
          entities: >
            [[[
              if (entity) {
                  let lights = [],
                      id = Boolean(entity.attributes.entity_id)
                          ? [entity.entity_id].concat(entity.attributes.entity_id)
                          : [entity.entity_id];

                  console.log("entity", entity)
                  console.table("id", id)
                  return lights;
              }
            ]]]
  variables:
    circle_input: >
      [[[
        if (entity) {
            // if light group get brightness from child to remove bounce
            let child = entity.attributes.entity_id,
                brightness = child && states[child[0]].attributes.brightness
                    ? Math.round(states[child[0]].attributes.brightness / 2.54)
                    : Math.round(entity.attributes.brightness / 2.54);
            return brightness === 0 && entity.state !== 'off'
                ? 1
                : brightness
        }
      ]]]
    circle_input_unit: '%'

I removed the code that draws the mushroom cards, and added some logging.

open dev tools and see what the results are.

This is what i get

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