1- import { css , html , LitElement , nothing } from 'lit' ;
1+ import { css , html , LitElement } from 'lit' ;
22import { property , state } from 'lit/decorators.js' ;
3- import { mdiArrowLeft } from '@mdi/js' ;
3+ import {
4+ mdiAlphaABoxOutline ,
5+ mdiArrowLeft ,
6+ mdiDotsVertical ,
7+ mdiGrid ,
8+ mdiHome ,
9+ mdiHomeImportOutline ,
10+ mdiListBoxOutline ,
11+ } from '@mdi/js' ;
412import Store from '../model/store' ;
513import '../upstream/ha-media-player-browse' ;
614import { MEDIA_ITEM_SELECTED } from '../constants' ;
715import { customEvent } from '../utils/utils' ;
816import { MediaPlayerItem } from '../types' ;
917
18+ type LayoutType = 'auto' | 'grid' | 'list' ;
19+
1020interface NavigateId {
1121 media_content_id ?: string ;
1222 media_content_type ?: string ;
1323 title ?: string ;
1424}
1525
26+ const START_PATH_KEY = 'sonos-card-media-browser-start' ;
27+ const LAYOUT_KEY = 'sonos-card-media-browser-layout' ;
28+
29+ // Module-level state to persist across section switches (resets on page reload)
30+ let currentPath : NavigateId [ ] | null = null ;
31+ let currentPathTitle = '' ;
32+
1633export class MediaBrowser extends LitElement {
1734 @property ( { attribute : false } ) store ! : Store ;
18- @state ( ) private navigateIds : NavigateId [ ] = [ { media_content_id : undefined , media_content_type : undefined } ] ;
35+ @state ( ) private navigateIds : NavigateId [ ] = [ ] ;
1936 @state ( ) private currentTitle = '' ;
37+ @state ( ) private isCurrentPathStart = false ;
38+ @state ( ) private layout : LayoutType = 'auto' ;
39+
40+ connectedCallback ( ) {
41+ super . connectedCallback ( ) ;
42+ this . initializeNavigateIds ( ) ;
43+ this . loadLayout ( ) ;
44+ }
45+
46+ private loadLayout ( ) {
47+ const savedLayout = localStorage . getItem ( LAYOUT_KEY ) as LayoutType | null ;
48+ if ( savedLayout && [ 'auto' , 'grid' , 'list' ] . includes ( savedLayout ) ) {
49+ this . layout = savedLayout ;
50+ }
51+ }
52+
53+ private setLayout ( layout : LayoutType ) {
54+ this . layout = layout ;
55+ localStorage . setItem ( LAYOUT_KEY , layout ) ;
56+ }
57+
58+ private handleMenuAction = ( ev : CustomEvent < { index : number } > ) => {
59+ const layouts : LayoutType [ ] = [ 'auto' , 'grid' , 'list' ] ;
60+ this . setLayout ( layouts [ ev . detail . index ] ) ;
61+ } ;
62+
63+ private getStartPath ( ) : NavigateId [ ] | null {
64+ const startPath = localStorage . getItem ( START_PATH_KEY ) ;
65+ if ( startPath ) {
66+ try {
67+ return JSON . parse ( startPath ) ;
68+ } catch {
69+ return null ;
70+ }
71+ }
72+ return null ;
73+ }
74+
75+ private initializeNavigateIds ( ) {
76+ // If we have a cached path from section switching, use it
77+ if ( currentPath ) {
78+ this . navigateIds = currentPath ;
79+ this . currentTitle = currentPathTitle ;
80+ this . updateIsCurrentPathStart ( ) ;
81+ return ;
82+ }
83+
84+ // On page reload: use saved start path if available, otherwise root
85+ const startPath = this . getStartPath ( ) ;
86+ if ( startPath ?. length ) {
87+ this . navigateIds = startPath ;
88+ const lastItem = this . navigateIds [ this . navigateIds . length - 1 ] ;
89+ this . currentTitle = lastItem ?. title || '' ;
90+ } else {
91+ this . navigateIds = [ { media_content_id : undefined , media_content_type : undefined } ] ;
92+ }
93+ this . updateIsCurrentPathStart ( ) ;
94+ }
95+
96+ private saveCurrentPath ( ) {
97+ currentPath = this . navigateIds ;
98+ currentPathTitle = this . currentTitle ;
99+ }
100+
101+ private updateIsCurrentPathStart ( ) {
102+ const startPath = this . getStartPath ( ) ;
103+ this . isCurrentPathStart = JSON . stringify ( this . navigateIds ) === JSON . stringify ( startPath ) ;
104+ }
105+
106+ private setAsStartPath = ( ) => {
107+ localStorage . setItem ( START_PATH_KEY , JSON . stringify ( this . navigateIds ) ) ;
108+ this . isCurrentPathStart = true ;
109+ } ;
20110
21111 render ( ) {
22112 const activePlayer = this . store . activePlayer ;
23113 const canGoBack = this . navigateIds . length > 1 ;
24114
25115 return html `
26- ${ canGoBack
27- ? html `
28- <div class= "header" >
29- <ha- icon- butto n .path = ${ mdiArrowLeft } @click = ${ this . goBack } > </ ha- icon- butto n>
30- <span class= "title" > ${ this . currentTitle } </ span>
31- </ div>
32- `
33- : nothing }
116+ <div class= "header" >
117+ ${ canGoBack
118+ ? html `<ha- icon- butto n .path = ${ mdiArrowLeft } @click = ${ this . goBack } > </ ha- icon- butto n> `
119+ : html `<div class= "spacer" > </ div> ` }
120+ <span class= "title" > ${ this . currentTitle || 'Media Browser' } </ span>
121+ <ha- icon- butto n
122+ .path = ${ this . isCurrentPathStart ? mdiHome : mdiHomeImportOutline }
123+ @click = ${ this . setAsStartPath }
124+ title= "Set as start page"
125+ > </ ha- icon- butto n>
126+ <ha- butto n- menu fixed cor ner= "BOTTOM_END" @action = ${ this . handleMenuAction } >
127+ <ha- icon- butto n slot= "trigger" .path = ${ mdiDotsVertical } > </ ha- icon- butto n>
128+ <ha- lis t- item graphic= "icon" >
129+ Auto
130+ <ha- svg- icon
131+ class= ${ this . layout === 'auto' ? 'selected' : '' }
132+ slot= "graphic"
133+ .path = ${ mdiAlphaABoxOutline }
134+ > </ ha- svg- icon>
135+ </ ha- lis t- item>
136+ <ha- lis t- item graphic= "icon" >
137+ Grid
138+ <ha- svg- icon
139+ class= ${ this . layout === 'grid' ? 'selected' : '' }
140+ slot= "graphic"
141+ .path = ${ mdiGrid }
142+ > </ ha- svg- icon>
143+ </ ha- lis t- item>
144+ <ha- lis t- item graphic= "icon" >
145+ List
146+ <ha- svg- icon
147+ class= ${ this . layout === 'list' ? 'selected' : '' }
148+ slot= "graphic"
149+ .path = ${ mdiListBoxOutline }
150+ > </ ha- svg- icon>
151+ </ ha- lis t- item>
152+ </ ha- butto n- menu>
153+ </ div>
34154 <sonos- ha- media- player- browse
35155 .hass = ${ this . store . hass }
36156 .entityId = ${ activePlayer . id }
37157 .navigateIds = ${ this . navigateIds }
158+ .preferredLayout = ${ this . layout }
38159 .action = ${ 'play' }
39160 @media-picked = ${ this . onMediaPicked }
40161 @media-browsed = ${ this . onMediaBrowsed }
@@ -47,6 +168,8 @@ export class MediaBrowser extends LitElement {
47168 this . navigateIds = this . navigateIds . slice ( 0 , - 1 ) ;
48169 const lastItem = this . navigateIds [ this . navigateIds . length - 1 ] ;
49170 this . currentTitle = lastItem ?. title || '' ;
171+ this . saveCurrentPath ( ) ;
172+ this . updateIsCurrentPathStart ( ) ;
50173 }
51174 } ;
52175
@@ -60,6 +183,8 @@ export class MediaBrowser extends LitElement {
60183 this . navigateIds = event . detail . ids ;
61184 const lastItem = this . navigateIds [ this . navigateIds . length - 1 ] ;
62185 this . currentTitle = lastItem ?. title || event . detail . current ?. title || '' ;
186+ this . saveCurrentPath ( ) ;
187+ this . updateIsCurrentPathStart ( ) ;
63188 } ;
64189
65190 static get styles ( ) {
@@ -73,17 +198,24 @@ export class MediaBrowser extends LitElement {
73198 .header {
74199 display : flex;
75200 align-items : center;
76- padding : 8px ;
201+ padding : 4 px 8px ;
77202 border-bottom : 1px solid var (--divider-color );
78203 background : var (--card-background-color );
79204 }
80205 .title {
206+ flex : 1 ;
81207 font-weight : 500 ;
82208 font-size : 1.1em ;
83- margin-left : 8px ;
84209 overflow : hidden;
85210 text-overflow : ellipsis;
86211 white-space : nowrap;
212+ text-align : center;
213+ }
214+ .spacer {
215+ width : 48px ;
216+ }
217+ ha-svg-icon .selected {
218+ color : var (--primary-color );
87219 }
88220 sonos-ha-media-player-browse {
89221 --mdc-icon-size : 24px ;
0 commit comments