Skip to content

Commit 1ee4e76

Browse files
committed
feat: Enhance media browser layout and functionality with new navigation features and button adjustments
1 parent 50de664 commit 1ee4e76

File tree

5 files changed

+165
-19
lines changed

5 files changed

+165
-19
lines changed

scripts/sync-upstream.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ sed -i '' \
7878
-e "s/gap: '16px'/gap: '8px'/g" \
7979
"$UPSTREAM_DIR/ha-media-player-browse.ts"
8080

81+
# Reduce play button size for smaller grid items
82+
echo "Reducing play button size..."
83+
sed -i '' \
84+
-e 's/--mdc-icon-button-size: 70px/--mdc-icon-button-size: 40px/g' \
85+
-e 's/--mdc-icon-size: 48px/--mdc-icon-size: 24px/g' \
86+
-e 's/top: calc(50% - 40px)/top: calc(50% - 20px)/g' \
87+
-e 's/right: calc(50% - 35px)/right: calc(50% - 20px)/g' \
88+
"$UPSTREAM_DIR/ha-media-player-browse.ts"
89+
8190
# Save version info
8291
echo "$RELEASE_TAG" > "$VERSION_FILE"
8392
echo "Synced: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> "$VERSION_FILE"

src/components/favorites-header.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class FavoritesHeader extends LitElement {
1414
return html`
1515
<div class="title">${favoritesConfig.title ?? 'All Favorites'}</div>
1616
<ha-icon-button
17-
hide=${favoritesConfig.hideBrowseMediaButton || nothing}
17+
hide=${!favoritesConfig.showBrowseMediaButton || nothing}
1818
@click=${() => this.store.mediaBrowseService.showBrowseMedia(this.store.activePlayer, this)}
1919
.path=${mdiPlayBoxMultiple}
2020
></ha-icon-button>

src/sections/media-browser.ts

Lines changed: 145 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,161 @@
1-
import { css, html, LitElement, nothing } from 'lit';
1+
import { css, html, LitElement } from 'lit';
22
import { 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';
412
import Store from '../model/store';
513
import '../upstream/ha-media-player-browse';
614
import { MEDIA_ITEM_SELECTED } from '../constants';
715
import { customEvent } from '../utils/utils';
816
import { MediaPlayerItem } from '../types';
917

18+
type LayoutType = 'auto' | 'grid' | 'list';
19+
1020
interface 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+
1633
export 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-button .path=${mdiArrowLeft} @click=${this.goBack}></ha-icon-button>
30-
<span class="title">${this.currentTitle}</span>
31-
</div>
32-
`
33-
: nothing}
116+
<div class="header">
117+
${canGoBack
118+
? html`<ha-icon-button .path=${mdiArrowLeft} @click=${this.goBack}></ha-icon-button>`
119+
: html`<div class="spacer"></div>`}
120+
<span class="title">${this.currentTitle || 'Media Browser'}</span>
121+
<ha-icon-button
122+
.path=${this.isCurrentPathStart ? mdiHome : mdiHomeImportOutline}
123+
@click=${this.setAsStartPath}
124+
title="Set as start page"
125+
></ha-icon-button>
126+
<ha-button-menu fixed corner="BOTTOM_END" @action=${this.handleMenuAction}>
127+
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}></ha-icon-button>
128+
<ha-list-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-list-item>
136+
<ha-list-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-list-item>
144+
<ha-list-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-list-item>
152+
</ha-button-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: 4px 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;

src/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export interface FavoritesConfig {
8888
customFavorites?: CustomFavorites;
8989
customThumbnails?: CustomFavoriteThumbnails;
9090
customThumbnailsIfMissing?: CustomFavoriteThumbnails;
91-
hideBrowseMediaButton?: boolean;
91+
showBrowseMediaButton?: boolean;
9292
hideHeader?: boolean;
9393
hideTitleForThumbnailIcons?: boolean;
9494
iconBorder?: string;
@@ -104,6 +104,10 @@ export interface FavoritesConfig {
104104
topItems?: string[];
105105
}
106106

107+
export interface MediaBrowserConfig {
108+
title?: string;
109+
}
110+
107111
export interface GroupsConfig {
108112
buttonWidth?: number;
109113
compact?: boolean;
@@ -181,6 +185,7 @@ export interface CardConfig extends LovelaceCardConfig {
181185
grouping?: GroupingConfig;
182186
volumes?: VolumesConfig;
183187
queue?: QueueConfig;
188+
mediaBrowser?: MediaBrowserConfig;
184189
}
185190

186191
export interface MediaArtworkOverride {

src/upstream/ha-media-player-browse.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,15 +1079,15 @@ export class HaMediaPlayerBrowse extends LitElement {
10791079
position: absolute;
10801080
transition: color 0.5s;
10811081
border-radius: var(--ha-border-radius-circle);
1082-
top: calc(50% - 40px);
1083-
right: calc(50% - 35px);
1082+
top: calc(50% - 20px);
1083+
right: calc(50% - 20px);
10841084
opacity: 0;
10851085
transition: opacity 0.1s ease-out;
10861086
}
10871087
10881088
.child .play:not(.can_expand) {
1089-
--mdc-icon-button-size: 70px;
1090-
--mdc-icon-size: 48px;
1089+
--mdc-icon-button-size: 40px;
1090+
--mdc-icon-size: 24px;
10911091
background-color: var(--primary-color);
10921092
color: var(--text-primary-color);
10931093
}

0 commit comments

Comments
 (0)