šŸ  HaCasa - A new modern dashboard

Awesome work! As MiniHass is somewhat dormant for personal development, itā€™ll be fun to follow this little project too :slight_smile:

Nice work! Going to have a playā€¦ :slight_smile:

This in combination with Bubble Card could be very polished indeed!

1 Like

Hello, I am trying to implement your progress bar and it is working perfectly but I would like to show the remaining time as text as it decreases in addition to the bar. Do you know any way to do it? The value is somewhere in the function but I donā€™t know how to get it out of it. Thanks in advance.

EDIT: I did it myself, Iā€™m not a programmer but it works

type: custom:button-card
entity: media_player.homepod_del_salon
show_icon: false
show_name: false
custom_fields:
  total: |
    [[[
        if (entity.attributes.media_duration !== undefined){
              var total = entity.attributes.media_duration; 
              return new Date(total * 1000).toISOString().substring(14, 19);
        }
    ]]]
  restante: |
    [[[
      if (entity.attributes.media_position !== undefined) {
      return ' ';}
    ]]]
  bar: |
    [[[
      if (entity.attributes.media_position !== undefined) {
      setTimeout(() => {
        let elt = this.shadowRoot,
            card = elt.getElementById('card'),
            container = elt.getElementById('container'),
            bar = elt.getElementById('bar');
        if (elt && card && container && bar) {
            card.insertBefore(bar, container);
              function update() {
                let mediaPositionUpdatedAt = entity.attributes.media_position_updated_at,
                    mediaPosition = entity.attributes.media_position,
                    mediaDuration = entity.attributes.media_duration,
                    mediaContentType = entity.attributes.media_content_type;
                let percentage = entity.state === 'paused'
                  ? (mediaPosition / mediaDuration * 100)
                  : entity.state === 'playing'
                    ? (((Date.now() / 1000) - (new Date(mediaPositionUpdatedAt).getTime() / 1000) + mediaPosition) / mediaDuration * 100)
                    : 0;
                let restante = entity.state === 'paused'
                  ? (mediaDuration - mediaPosition)
                  : entity.state === 'playing'
                    ? (mediaDuration - ((Date.now() / 1000) - (new Date(mediaPositionUpdatedAt).getTime() / 1000) + mediaPosition) )
                    : 0;
                bar.style.width = percentage.toFixed(1) + '%';
                elt.getElementById("restante").innerHTML = new Date(restante.toFixed(0) * 1000).toISOString().substring(14, 19);
                requestAnimationFrame(update);
              }
              requestAnimationFrame(update);
        }
      }, 0);
      return ' ';}
    ]]]
  progress: |
    [[[
      if (entity.attributes.media_position !== undefined) {
      return ' ';}
    ]]]
styles:
  grid:
    - grid-template-areas: '"total bar restante"'
    - grid-template-rows: min-content
    - grid-template-columns: min-content 1fr min-content
  custom_fields:
    progress:
      - background-color: rgba(0,0,0,0.1)
      - position: absolute
      - top: unset
      - bottom: 0px
      - height: 0.5rem
      - width: 100%
    bar:
      - background-color: '#00acc1'
      - position: absolute
      - bottom: 0px
      - left: 0%
      - top: unset
      - height: 0.5rem
      - z-index: 1
      - transition: 1s ease-out

Small update: Iā€™m pretty overwhelmed about all the positive reactions and ideas, Iā€™m so happy people like it.

Last week was pretty busy so Iā€™m going to note everything and get to work next week!

1 Like

this custom field should update according to the playing state, but there is some delay, probably because of the button card property how often it updates entities.

  progress_bar:
    styles:
      custom_fields:
        progress:
          - background-color: 'rgba(0,0,0,0.1)'
          - position: absolute
          - top: unset
          - bottom: 0px
          - height: 0.5rem
          - width: 100%
        bar:
          - background-color: '#00acc1'
          - position: absolute
          - bottom: 0px
          - left: 0%
          - top: unset
          - height: 0.5rem
          - z-index: 1
          - transition: 1s ease-out
        time:
          - position: absolute
          - bottom: 10px
          - right: 0px
          - color: var(--primary-text-color)
          - font-size: 0.7rem
          - padding: 0px 5px
          - z-index: 1
    custom_fields:
      time: >
        [[[
          if (entity.attributes.media_position !== undefined) {
            let mediaPosition = entity.attributes.media_position,
                mediaDuration = entity.attributes.media_duration,
                mediaPositionUpdatedAt = entity.attributes.media_position_updated_at;

            function updateTimeLeft() {
              if (entity.state === 'playing') {
                let currentPosition = mediaPosition + ((Date.now() - new Date(mediaPositionUpdatedAt).getTime()) / 1000);
                let timeLeft = mediaDuration - currentPosition;
                let minutes = Math.floor(timeLeft / 60);
                let seconds = Math.floor(timeLeft % 60);
                return `${minutes}:${seconds < 10 ? '0' + seconds : seconds}`;
              } else {
                let timeLeft = mediaDuration - mediaPosition;
                let minutes = Math.floor(timeLeft / 60);
                let seconds = Math.floor(timeLeft % 60);
                return `${minutes}:${seconds < 10 ? '0' + seconds : seconds}`;
              }
            }
            let timeLeftString = updateTimeLeft();

            (function loop() {
              setTimeout(() => {
                timeLeftString = updateTimeLeft();
                loop();
              }, 100);
            })();
            return timeLeftString;
          }
        ]]]

      bar: >
        [[[
          if (entity.attributes.media_position !== undefined) {
          setTimeout(() => {
            let elt = this.shadowRoot,
                card = elt.getElementById('card'),
                container = elt.getElementById('container'),
                bar = elt.getElementById('bar');
            if (elt && card && container && bar) {
                card.insertBefore(bar, container);
                  function update() {
                    let mediaPositionUpdatedAt = entity.attributes.media_position_updated_at,
                        mediaPosition = entity.attributes.media_position,
                        mediaDuration = entity.attributes.media_duration,
                        mediaContentType = entity.attributes.media_content_type;
                    let percentage = entity.state === 'paused'
                      ? (mediaPosition / mediaDuration * 100)
                      : entity.state === 'playing'
                        ? (((Date.now() / 1000) - (new Date(mediaPositionUpdatedAt).getTime() / 1000) + mediaPosition) / mediaDuration * 100)
                        : 0;
                    bar.style.width = percentage.toFixed(1) + '%';
                    requestAnimationFrame(update);
                  }
                  requestAnimationFrame(update);
            }
          }, 0);
          return ' ';}
        ]]]
      progress: >
        [[[
          if (entity.attributes.media_position !== undefined) {
          return ' ';}
        ]]]
      - type: custom:button-card
        template:
          - custom_card_mediaplayer_music
          - progress_bar
        entity: media_player.spotify
        triggers_update: all

CleanShot 2024-07-05 at 18.44.07

2 Likes

Very hyped for this! Looking forward to seeing this available in HACS

Hello @damiantje99,
is it possible that you share a Photo wich shows how to place the yaml files and folders from your HaCasa Dashboard like this:


That will be great! Thanks in advance:-)

itā€™s not made for Minimalist-UI, itā€™s made standard Lovelace dashboard.

so do like this:

and add the configure.yaml like:

lovelace:
	mode: storage
	dashboards:
	  lovelace-andyblac:
	    mode: yaml
	    title: Main
	    icon: phu:imac
	    show_in_sidebar: true
	    filename: dashboard/AndyBlac/main.yaml
	  lovelace-hacasa:
	    mode: yaml
	    title: HaCasa
	    icon: mdi:script
	    show_in_sidebar: true
	    filename: dashboard/HaCasa/main.yaml

any way to make it look nice on the iPad?

Love the design and project, btw you dont reaaally need kiosk-mode, you can just add this to your theme-file and the header will disappear:

####################### LIGHT MODE ####################
HaCasa:

## Add this to your theme-file ##
  card-mod-theme: HaCasa
  card-mod-root: |
    .header{
        display: none;
    }
    #view {
        padding: 0 !important;
        height: 100vh !important;
    }

Iā€™ve done this and created a duplicate of the theme, which i added ā€œDesktopā€ to the name, that way i have two separate themes and dont have to fiddle with kiosk-mode

1 Like

Like this approach. No need for a extra plugin and less is more :slight_smile:.

Will add it to the next release!

1 Like

Hi Everyone,

So, I decided to really dive back in this time :diving_mask:. I made some changes to the layout and also brought back some color. Iā€™m not sure what I like the most, but I hope you guys can help me out with that one :pray:

This is just the redesigned frontpage, but there will be more to come soon!


I like it!
What stood out to me with this ā€˜themeā€™ / project was the simplicity compared to the ā€˜stockā€™ HA and Minimalist UIā€¦

I wouldnā€™t mind a cover and/or lock card any time soon :innocent:

So would you say, this new ā€˜themeā€™ I posted is still in the ā€˜stood outā€™ category or you like the other one better?

Also, there are a few people who created a cover and a lock card. I havenā€™t had the chance to go over them, but the code etc. is available in the Discord server (if you havenā€™t joined yet).

1 Like

What I meant was that I like the way you have done it much better!

My frontend is in a sort of limbo where I have a mix of elements from different projects (mushroom, minimalist, custom tiles, stack-in-cards and this) but since Iā€™ve got my house pretty well automated I havenā€™t bothered to dedicate myself to a single style UI-wise.

From all of those projects out there I think I like the design of yours the best, and a little more color is nice. ( as long as itā€™s following a common line(ein rother Faden) :smile: ā€¦Even though my whole house is beigeā€¦ lol)

I got the same error the first time. Fix was to change HaCasa to ha-casa. HA needs a - and it needs to be lower case. See below

lovelace:
  mode: "storage"
  resources:
    - url: "/hacsfiles/button-card/button-card.js"
      type: "module"
    - url: "/hacsfiles/my-cards/my-cards.js"
      type: "module"
    - url: "/hacsfiles/kiosk-mode/kiosk-mode.js"
      type: module
  dashboards:
    ha-casa:
      mode: "yaml"
      title: HaCasa
      icon: mdi:script
      show_in_sidebar: true
      filename: "dashboard/HaCasa/main.yaml"