A different take on designing a Lovelace UI

That makes a lot of sense.

The wall I’m hitting however is that regardless of making a copy of the template, is that I can’t seem to figure out how to add the extra scene cards into the code that the light template uses.

I don’t have a great understanding of how to template in this way, so I haven’t managed to get the pop-up to display both cards together while still having the light card auto-populate entities based on the group that calls it.

oh ok then, this is an example I just throw together

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: vertical-stack
          cards:
            - 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;
                  }
                ]]]
            - type: custom:mod-card
              card_mod:
                style:
                  hui-horizontal-stack-card$: |
                    #root {
                      justify-content: space-evenly !important;
                      padding: var(--tablet-popup-button-padding);
                    }
              card:
                type: horizontal-stack
                cards:
                  - type: custom:button-card
                    name: scene 1
                    icon: mdi:fire

                  - type: custom:button-card
                    name: scene 2
                    icon: mdi:party-popper
  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: '%'

so in the code above entities: is just the entities section of a normal entities card, if you look at other popups, you will see that content can only be passed one card, the way around this is to pass a layout card or in this case a vertical-stack, then the vertical-stack can be passed the entities card, and our new card, in this case just a horizontal-stack with some buttons, but you can replace that with your example from above.

The hard part is going to be getting the list of scenes passed to the template to dynamically create the buttons

2 Likes

that was Fun

after little more time this is what I have

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: vertical-stack
          cards:
            - 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;
                  }
                ]]]
            - type: custom:mod-card
              card_mod:
                style:
                  hui-horizontal-stack-card$: |
                    #root {
                      justify-content: space-evenly !important;
                      padding: var(--tablet-popup-button-padding);
                    }
              card:
                type: horizontal-stack
                cards: >
                  [[[
                    if (variables.scene_groupe) {
                      console.log(states);
                      console.log(variables.scene_groupe);

                      let scenes = [];
                      for (let i = 0; i < variables.scene_groupe.length; i++) {
                        let e = "scene." + variables.scene_groupe[i]
                          scenes.push({"type": "custom:mushroom-chips-card",
                          "alignment": "center",

                        "chips":[
                        {
                          "type": "entity",
                          "entity": e,
                          "content_info": "name",
                          "tap_action": {
                            "action": "call-service",
                            "service": "scene.turn_on",
                            "target": {
                              "entity_id": e
                            }
                            }
                          }
                        ]
                    });
                      }
                      return scenes;

                    }
                    return []
                  ]]]
  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: '%'

used

- type: custom:button-card
  entity: light.light_kitchen_cabinet
  name: Cabinet
  template:
    - light
    - icon_wled
  variables:
    scene_groupe: ['t1','t2']

2 Likes

Oh this is fantastic.

I knew I was going to struggle with card mod a bit to get things displaying nicely, but the dynamic scenes might have been beyond me!

This will be a useful reference for turning other pop-ups I have into templates too.

Thank you so much!

happy to help, as I said this one was fun.

I would like to auto add the scenes based on if the scene has any of the provided lights in it. im going to give that a go now.

if you have any other issues feel free to ask, I love that you had made a good attempt and I could see what you wanted based on the information that you provided.

1 Like

and we have improvements, @Mattias_Persson, this might be something to add to the base repo, it could use some css magic

now it should auto detect any scenes that use any of the lights and add them, if you have a light but you don’t want the scenes to be added then you can add the following on the button card

  variables:
    show_scenes: false

templates

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: vertical-stack
          cards:
            - 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;
                  }
                ]]]
            - type: custom:mod-card
              card_mod:
                style:
                  hui-horizontal-stack-card$: |
                    #root {
                      justify-content: space-evenly !important;
                      padding: var(--tablet-popup-button-padding);
                    }
              card:
                type: horizontal-stack
                cards: >
                  [[[
                    if (entity) {
                      let id = Boolean(entity.attributes.entity_id)
                            ? [entity.entity_id].concat(entity.attributes.entity_id)
                            : [entity.entity_id];
                      let foundSenes = Object.keys(states).filter(s => s.indexOf("scene.") > -1).map(s=>states[s]).filter(s=>s.attributes.entity_id.some(e=>id.includes(e))),
                      scenes = [];
                      for (let i = 0; i < foundSenes.length; i++) {
                        scenes.push({"type": "custom:mushroom-chips-card",
                          "alignment": "center",
                          "chips":[
                          {
                            "type": "entity",
                            "entity": foundSenes[i].entity_id,
                            "content_info": "name",
                            "tap_action": {
                              "action": "call-service",
                              "service": "scene.turn_on",
                              "target": {
                                "entity_id": foundSenes[i].entity_id
                              }
                              }
                            }
                          ]
                      });
                      }
                      return variables.show_scenes? scenes: [];
                    }
                  ]]]
  variables:
    show_scenes: true
    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: '%'

3 Likes

Slick as.

Other than some strange scrollbar behaviour, this is perfect.

On PC, depending on screen layout, entity count (I have 5 including the parent group) and which lights are toggled on/off, both vertical and horizontal scrollbars can appear and disappear in very unpredictable ways.

It’s a very small scroll, and isn’t very noticeable on mobile without deliberately dragging.

try replacing --tablet-popup-button-padding with --tablet-card-content-padding

No luck unfortunately.

I’m not sure if the hui-horizontal-stack-card$ or perhaps mod-card itself is functioning in the card at all, I can’t seem to change or add anything there to any effect.

I can confirm that hui-horizontal-stack-card$ is working, I set it to 1000px and could see the change,

FYI if you change any template file or file added via include to a dashboard, you need to also make a change to ui-lovelace.yaml for the change to take effect, this is a home assistant “issue”.

Just remove a letter, save add the same letter back and then save again. if you dont see this message then your change might not take effect

for me on a Mac, using chrome. I can get vertical scrolling but not horizontal even with 6 scenes and long names on my lights.


Oh yeah, adding 1000px defs has an effect (I’m the worst at using or understanding cardmod haha)

I use

alias: Reload lovelace
sequence:
  - service: browser_mod.javascript
    data:
      code: lovelace_reload()
mode: single

mapped to a keyboard shortcut using hass-agent to deal with the updating!

2 Likes

I don’t have any long names, and also noticed scrolls appearing if the card is present bunt entirely empty.

For both chrome and firefox, I get this sort of behaviour:
scroll

I also tested it without the scene with japanese characters just in case of something funky.

The HA mobile app also seems to have this scroll space behaviour, but is less evident as it doesn’t show scrollbars.

nice!
well in that case, I would recommend you play with the CSS now that you have the auto adding,
Im not a fan of the chips with no background, they feel lots, but I also dont have any scenes

I dont think vertical scrolling can be avoided but I would use css to try and prevent the horizontal scrolling that you are getting.

ones you have tweaked thing id love to see how you are using it.

1 Like

Hi
I have an error in logs : TemplateError('TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'') while processing template 'Template("{{ this.attributes.values() | sum }}")' for attribute '_attr_native_value' in entity 'sensor.template_updates'
This error come from version_updates.yaml, line 51 {{ this.attributes.values() | sum }}
How to fix it ? Did I miss something in my fles ?

Thanks

Hello all,
I have a question about the persons and the last_change sensor.
I would have the quite gladly exchanged against my proximity sensor, so that there displays the current distance instead of the last change.
However, dipping the sensor through the proximity has not helped :confused:

Update: Figured it out. You have to modify the fire-dom-event action in each of your popup yaml files as described here.

Just updated to browser mod 2 today and I noticed I can’t open any popups. I haven’t changed anything in the ui-lovelace.yaml file, but here’s an example of one of my buttons with a tap action to open a popup:

cards:
  - type: button
    icon: mdi:security
    tap_action:
      !include popup/sidebar_security.yaml
    hold_action:
      action: none

Searched through this thread to see if anyone else has already had this issue but couldn’t find anything. Any pointers?

sidebar_security.yaml is a custom popup, browser mod 2 is completely rewritten FAQ

Are all popups broken or just popups that you have written?

Have you migrated your custom popups to the new browser mod 2 syntax? like so

Have you completed the full browser mod install? Quickstart

This doenst work for me.

Have to change config to browser_mod w

action: fire-dom-event
browser_mod:
  service: browser_mod.popup
  data:
    title: Cameras
    style:
      .: |
        :host .main-title {
          pointer-events: none;
        }
      $: |
        .mdc-dialog__surface {
          background: transparent !important;
          border-style: none !important;
          border: 0px !important;
          box-shadow: none;
        }

    card:
      type: vertical-stack
      cards:
        - type: custom:layout-card
          layout_type: grid
          layout:
            grid-gap: 0.4vw
            #grid-template-columns: 860px 860px
            grid-template-rows: 540px 540px
            grid-template-areas: |
              "cam1 cam2"
              "cam3 cam4"

            mediaquery:
              #hide_header: false
              #phone
              "(max-width: 800px)":
                grid-gap: 1.5vw
                grid-template-columns: auto
                grid-template-rows: auto auto auto auto
                grid-template-areas: |
                  "cam1"
                  "cam2"
                  "cam3"
                  "cam4"

              #tablet
              "(max-width: 2000px)":
                grid-gap: 0.5vw
                grid-template-rows: 23vw 23vw
                grid-template-areas: |
                  "cam1 cam2"
                  "cam3 cam4"

          cards:
            - type: picture-entity
              entity: camera.garagemfrente
              aspect_ratio: 50%
              show_info: false
              show_state: false
              show_name: false
              camera_view: auto
              view_layout:
                grid-area: cam1

            - type: picture-entity
              entity: camera.varandafrente
              show_info: false
              show_state: false
              show_name: false
              camera_view: auto
              view_layout:
                grid-area: cam2

            - type: picture-entity
              entity: camera.varandafundo
              show_info: false
              show_state: false
              show_name: false
              camera_view: auto
              view_layout:
                grid-area: cam3

            - type: picture-entity
              entity: camera.terraco
              show_info: false
              show_state: false
              show_name: false
              camera_view: auto
              view_layout:
                grid-area: cam4

WIP kitchen timer card

this weekend I worked on a kitchen timer card for my kitchen dashboard, this is still a work in progress. I wanted to put it up to see if anyone had any thoughts

there are 3 sections,
the 1st is the time input that I got from this post (thanks @Mattias_Persson and @ParalaX)
this is where how you can enter a time for the timer to run in hours and minutes, the last enter time is saved, and tapping the button will start the timer

the 2ed sections, is the countdown, if a timer is running this is displayed that shows how long the timer has remaining, and can be tapped to stop a running timer, a lot of work went into drawing the outline around the circle as the timer ticks down the circle outline will fill in.


The last section is just 4 buttons that can be used to start common timers with one click, this section can be shown by swiping

setup

you will need an input date time and a timer Entity, along with the following script that is used to start the timer with the input from the card

script

kitchen_timer:
  alias: calculates the time given for the timer and then starts the kitchen timer,
  fields:
    time_input:
      description: Entity id of the date time input
      example: input_datetime.kitchen_timer
    timer:
      description: Entity id of the timer to set
      example: timer.kitchen_timer
  sequence:
  - service: timer.start
    data:
      duration:  '{{ states(time_input) }}'
    target:
      entity_id: '{{ timer }}'
  mode: single

code

use

      #################################################
      #                                               #
      #                     Timer                     #
      #                                               #
      #################################################

      - type: grid
        title: Timer ↔
        view_layout:
          grid-area: timer
        columns: 1
        cards: !include decks/kitchen_timer_deck.yaml

kitchen_timer_deck.yaml

  #################################################
  #                                               #
  #             kitchen timer deck                #
  #                                               #
  #################################################
- type: custom:swipe-card
  start_card: 1
  parameters:
    roundLengths: true
    effect: coverflow
    speed: 650
    spaceBetween: 20
    threshold: 7
    coverflowEffect:
      rotate: 80
      depth: 300
  cards:
# page 1
    - type: horizontal-stack
      cards:
      # show input if timer is idle, else show countdown
        - type: conditional
          conditions:
            - entity: timer.kitchen_timer
              state: idle
          card:
            #################################################
            #                                               #
            #                     Input                     #
            #                                               #
            #################################################
            type: custom:button-card
            name: >
              [[[ return '&nbsp;'; ]]]
            state_display: Kitchen Timer
            template:
              - base
            custom_fields:
              time:
                card:
                  type: custom:time-picker-card
                  entity: input_datetime.kitchen_timer
                  hide:
                    name: true
                  card_mod:
                    style:
                      .: |
                        .time-picker-content {
                          justify-content: space-evenly !important;
                          padding-right: 1%;
                        }
                        .time-separator {
                          color: rgba(255, 255, 255, 0.3);
                        }
                        .time-picker-row {
                          display: block !important;
                          padding: 0 !important;
                          overflow: hidden !important;
                        }
                        .time-input {
                          border-radius: 10px;
                        }
                        :host {
                          --ha-card-border-width: 0px;
                          --time-picker-elements-background-color: rgba(0, 0, 0, 0.15);
                          --time-picker-icon-color: rgba(255, 255, 255, 0.4);
                          --time-picker-text-color: rgba(255, 255, 255, 0.6);
                          --time-picker-control-padding: 6px;
                        }
                      time-unit:
                        $: |
                          .time-input {
                            border-radius: 0.4vw;
                          }
                          .time-unit {
                            padding: 0 !important;
                          }
            styles:
              custom_fields:
                time:
                  - position: absolute
                  - width: 100%
                  - height: 80%
                  - clip-path: inset(0 round var(--custom-button-card-border-radius))
                  - left: 0
                  - top: 15%
            tap_action:
              action: call-service
              service: script.kitchen_timer
              service_data:
                time_input: input_datetime.kitchen_timer
                timer: timer.kitchen_timer
              
        - type: conditional
          conditions:
            - entity: timer.kitchen_timer
              state_not: idle
          card:
            #################################################
            #                                               #
            #                   Countdown                   #
            #                                               #
            #################################################
            type: custom:button-card
            entity: timer.kitchen_timer
            template:
              - base
            tap_action:
              action: call-service
              service: timer.cancel
              service_data:
                entity_id: timer.kitchen_timer
            custom_fields:
              countdown: >
                [[[
                  setTimeout(() => {

                    let elt = this.shadowRoot,
                    circle_stroke = elt.getElementById('circle_stroke'),
                    r = 22.1,
                    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 = 'var(--c-stroke-width-dragging)';
                  }, 0);

                  let r = 22.1,
                  c = r * 2 * Math.PI,
                  state = true,
                  input = variables.circle_input || ' ';

                  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);
                        
                      }
                      #circle_stroke{
                        stroke: ${'var(--c-stroke-color-on)' };
                        fill: ${'var(--c-fill-color-on)'};
                      }
                      #circle_bg {
                        stroke: ${'var(--c-stroke-color-off)'};
                        fill: ${'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}"/>
                    <circle id="circle_bg" cx="25" cy="25" r="${r}"/>
                  </svg>       `;           
                ]]]
            styles:
              state:
                - position: relative
                - line-height: 0px
                - overflow: visible
                - top: -2.5em
                - margin: auto
                - display: initial
                - --button-card-font-size: 4vw
              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: 1
                - --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:
                countdown:
                  - position: absolute
                  - width: 100%
                  - height: 100%
                  - left: 0
                  - top: -8%
                  - display: initial
                  - opacity: 1
                  - justify-self: end
# page 2
    - type: grid
      columns: 2
      cards:
        - type: custom:button-card
          entity: binary_sensor.template_living_room_tv_source_plex
          template: 
            - base
          name: 5 min
          show_state: false
          custom_fields:
            icon: <ha-icon icon="mdi:clock-time-two-outline"></ha-icon>
          tap_action:
            action: call-service
            service: timer.start
            service_data:
              duration: "00:05:00"
              entity_id: timer.kitchen_timer

        - type: custom:button-card
          entity: binary_sensor.template_living_room_tv_source_youtube
          template: 
            - base
          name: 25 min
          show_state: false
          custom_fields:
            icon: <ha-icon icon="mdi:clock-time-five-outline"></ha-icon>
          tap_action:
            action: call-service
            service: timer.start
            service_data:
              duration: "00:25:00"
              entity_id: timer.kitchen_timer

        - type: custom:button-card
          entity: binary_sensor.template_living_room_tv_source_netflix
          template: 
            - base
          name: 45 min
          show_state: false
          custom_fields:
            icon: <ha-icon icon="mdi:clock-time-eight-outline"></ha-icon>
          tap_action:
            action: call-service
            service: timer.start
            service_data:
              duration: "00:45:00"
              entity_id: timer.kitchen_timer

        - type: custom:button-card
          entity: binary_sensor.template_living_room_tv_source_funimation
          template: 
            - base
          name: 1 hour
          show_state: false
          custom_fields:
            icon: <ha-icon icon="mdi:clock-time-twelve-outline"></ha-icon>
          tap_action:
            action: call-service
            service: timer.start
            service_data:
              duration: "01:00:00"
              entity_id: timer.kitchen_timer
14 Likes

hi,

i have the same Problem.