Skip to content

Commit 188fb7e

Browse files
committed
[ 1.0.24 ] * Added new userpreset type filtersection, which can be used to quickly display a section with the specified filter criteria applied. More info can be found on the [wiki docs](https://github.com/thlucas1/spotifyplus_card/wiki/Configuration-Options#userpresets-filter-section-media).
1 parent d8adb37 commit 188fb7e

File tree

9 files changed

+246
-13
lines changed

9 files changed

+246
-13
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ Change are listed in reverse chronological order (newest to oldest).
66

77
<span class="changelog">
88

9+
###### [ 1.0.24 ] - 2024/12/22
10+
11+
* Added new userpreset type `filtersection`, which can be used to quickly display a section with the specified filter criteria applied. More info can be found on the [wiki docs](https://github.com/thlucas1/spotifyplus_card/wiki/Configuration-Options#userpresets-filter-section-media).
12+
913
###### [ 1.0.23 ] - 2024/12/20
1014

1115
* This release requires the SpotifyPlus Integration v1.0.73+ release; please make sure you update the SpotifyPlus integration prior to updating this SpotifyPlus Card release.
1216
* Fixed hidden volume controls, which was caused by a bug introduced with v1.0.20.
13-
* Added the ability to disable image caching for userpreset images. More info can be found on the [wiki docs](https://github.com/thlucas1/spotifyplus_card/wiki/Configuration-Options#userpresets-image-url-caching.)
17+
* Added the ability to disable image caching for userpreset images. More info can be found on the [wiki docs](https://github.com/thlucas1/spotifyplus_card/wiki/Configuration-Options#userpresets-image-url-caching).
1418

1519
###### [ 1.0.22 ] - 2024/12/18
1620

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 - 2024 Todd Lucas @thlucas1
3+
Copyright (c) 2019 - 2025 Todd Lucas @thlucas1
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

SpotifyPlusCard.njsproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@
144144
<TypeScriptCompile Include="src\events\category-display.ts">
145145
<SubType>Code</SubType>
146146
</TypeScriptCompile>
147+
<TypeScriptCompile Include="src\events\filter-section-media.ts">
148+
<SubType>Code</SubType>
149+
</TypeScriptCompile>
147150
<TypeScriptCompile Include="src\events\search-media.ts" />
148151
<TypeScriptCompile Include="src\sections\category-browser.ts" />
149152
<TypeScriptCompile Include="src\sections\episode-fav-browser.ts" />

src/card.ts

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import './editor/editor';
2525

2626
// our imports.
2727
import { SEARCH_MEDIA, SearchMediaEventArgs } from './events/search-media';
28+
import { FILTER_SECTION_MEDIA, FilterSectionMediaEventArgs } from './events/filter-section-media';
2829
import { CATEGORY_DISPLAY, CategoryDisplayEventArgs } from './events/category-display';
2930
import { EDITOR_CONFIG_AREA_SELECTED, EditorConfigAreaSelectedEventArgs } from './events/editor-config-area-selected';
3031
import { PROGRESS_STARTED } from './events/progress-started';
@@ -36,7 +37,18 @@ import { CardConfig } from './types/card-config';
3637
import { CustomImageUrls } from './types/custom-image-urls';
3738
import { SearchMediaTypes } from './types/search-media-types';
3839
import { SearchBrowser } from './sections/search-media-browser';
40+
import { FavBrowserBase } from './sections/fav-browser-base';
41+
import { AlbumFavBrowser } from './sections/album-fav-browser';
42+
import { ArtistFavBrowser } from './sections/artist-fav-browser';
43+
import { AudiobookFavBrowser } from './sections/audiobook-fav-browser';
3944
import { CategoryBrowser } from './sections/category-browser';
45+
import { DeviceBrowser } from './sections/device-browser';
46+
import { EpisodeFavBrowser } from './sections/episode-fav-browser';
47+
import { PlaylistFavBrowser } from './sections/playlist-fav-browser';
48+
import { RecentBrowser } from './sections/recent-browser';
49+
import { ShowFavBrowser } from './sections/show-fav-browser';
50+
import { TrackFavBrowser } from './sections/track-fav-browser';
51+
import { UserPresetBrowser } from './sections/userpreset-browser';
4052
import { formatTitleInfo, removeSpecialChars } from './utils/media-browser-utils';
4153
import { BRAND_LOGO_IMAGE_BASE64, BRAND_LOGO_IMAGE_SIZE, FOOTER_ICON_SIZE_DEFAULT } from './constants';
4254
import {
@@ -98,8 +110,19 @@ export class Card extends LitElement {
98110
@state() private cancelLoader!: boolean;
99111
@state() private playerId!: string;
100112

113+
// card section references.
101114
@query("#elmSearchMediaBrowserForm", false) private elmSearchMediaBrowserForm!: SearchBrowser;
102115
@query("#elmCategoryBrowserForm", false) private elmCategoryBrowserForm!: CategoryBrowser;
116+
@query("#elmAlbumFavBrowserForm", false) private elmAlbumFavBrowserForm!: AlbumFavBrowser;
117+
@query("#elmArtistFavBrowserForm", false) private elmArtistFavBrowserForm!: ArtistFavBrowser;
118+
@query("#elmAudiobookFavBrowserForm", false) private elmAudiobookFavBrowserForm!: AudiobookFavBrowser;
119+
@query("#elmDeviceBrowserForm", false) private elmDeviceBrowserForm!: DeviceBrowser;
120+
@query("#elmEpisodeFavBrowserForm", false) private elmEpisodeFavBrowserForm!: EpisodeFavBrowser;
121+
@query("#elmPlaylistFavBrowserForm", false) private elmPlaylistFavBrowserForm!: PlaylistFavBrowser;
122+
@query("#elmRecentBrowserForm", false) private elmRecentBrowserForm!: RecentBrowser;
123+
@query("#elmShowFavBrowserForm", false) private elmShowFavBrowserForm!: ShowFavBrowser;
124+
@query("#elmTrackFavBrowserForm", false) private elmTrackFavBrowserForm!: TrackFavBrowser;
125+
@query("#elmUserPresetBrowserForm", false) private elmUserPresetBrowserForm!: UserPresetBrowser;
103126

104127
/** Indicates if createStore method is executing for the first time (true) or not (false). */
105128
private isFirstTimeSetup: boolean = true;
@@ -168,19 +191,19 @@ export class Card extends LitElement {
168191
${
169192
this.playerId
170193
? choose(this.section, [
171-
[Section.ALBUM_FAVORITES, () => html`<spc-album-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-album-fav-browser>`],
172-
[Section.ARTIST_FAVORITES, () => html`<spc-artist-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-artist-fav-browser>`],
173-
[Section.AUDIOBOOK_FAVORITES, () => html`<spc-audiobook-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-audiobook-fav-browser>`],
194+
[Section.ALBUM_FAVORITES, () => html`<spc-album-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmAlbumFavBrowserForm"></spc-album-fav-browser>`],
195+
[Section.ARTIST_FAVORITES, () => html`<spc-artist-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmArtistFavBrowserForm"></spc-artist-fav-browser>`],
196+
[Section.AUDIOBOOK_FAVORITES, () => html`<spc-audiobook-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmAudiobookFavBrowserForm"></spc-audiobook-fav-browser>`],
174197
[Section.CATEGORYS, () => html`<spc-category-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmCategoryBrowserForm"></spc-category-browser>`],
175-
[Section.DEVICES, () => html`<spc-device-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-device-browser>`],
176-
[Section.EPISODE_FAVORITES, () => html`<spc-episode-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-episode-fav-browser>`],
198+
[Section.DEVICES, () => html`<spc-device-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmDeviceBrowserForm"></spc-device-browser>`],
199+
[Section.EPISODE_FAVORITES, () => html`<spc-episode-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmEpisodeFavBrowserForm"></spc-episode-fav-browser>`],
177200
[Section.PLAYER, () => html`<spc-player id="spcPlayer" .store=${this.store}></spc-player>`],
178-
[Section.PLAYLIST_FAVORITES, () => html`<spc-playlist-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-playlist-fav-browser>`],
179-
[Section.RECENTS, () => html`<spc-recent-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-recents-browser>`],
201+
[Section.PLAYLIST_FAVORITES, () => html`<spc-playlist-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmPlaylistFavBrowserForm"></spc-playlist-fav-browser>`],
202+
[Section.RECENTS, () => html`<spc-recent-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmRecentBrowserForm"></spc-recents-browser>`],
180203
[Section.SEARCH_MEDIA, () => html`<spc-search-media-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmSearchMediaBrowserForm"></spc-search-media-browser>`],
181-
[Section.SHOW_FAVORITES, () => html`<spc-show-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-show-fav-browser>`],
182-
[Section.TRACK_FAVORITES, () => html`<spc-track-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-track-fav-browser>`],
183-
[Section.USERPRESETS, () => html`<spc-userpreset-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected}></spc-userpresets-browser>`],
204+
[Section.SHOW_FAVORITES, () => html`<spc-show-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmShowFavBrowserForm"></spc-show-fav-browser>`],
205+
[Section.TRACK_FAVORITES, () => html`<spc-track-fav-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmTrackFavBrowserForm"></spc-track-fav-browser>`],
206+
[Section.USERPRESETS, () => html`<spc-userpreset-browser .store=${this.store} @item-selected=${this.onMediaListItemSelected} id="elmUserPresetBrowserForm"></spc-userpresets-browser>`],
184207
[Section.UNDEFINED, () => html`<div class="spc-not-configured">SpotifyPlus card configuration error.<br/>Please configure section(s) to display.</div>`],
185208
])
186209
: html`<div class="spc-initial-config">Welcome to the SpotifyPlus media player card.<br/>Start by configuring a media player entity.</div>`
@@ -418,6 +441,7 @@ export class Card extends LitElement {
418441
this.addEventListener(PROGRESS_STARTED, this.onProgressStartedEventHandler);
419442
this.addEventListener(SEARCH_MEDIA, this.onSearchMediaEventHandler);
420443
this.addEventListener(CATEGORY_DISPLAY, this.onCategoryDisplayEventHandler);
444+
this.addEventListener(FILTER_SECTION_MEDIA, this.onFilterSectionMediaEventHandler);
421445

422446
// only add the following events if card configuration is being edited.
423447
if (isCardInEditPreview(this)) {
@@ -447,6 +471,7 @@ export class Card extends LitElement {
447471
this.removeEventListener(PROGRESS_STARTED, this.onProgressStartedEventHandler);
448472
this.removeEventListener(SEARCH_MEDIA, this.onSearchMediaEventHandler);
449473
this.removeEventListener(CATEGORY_DISPLAY, this.onCategoryDisplayEventHandler);
474+
this.removeEventListener(FILTER_SECTION_MEDIA, this.onFilterSectionMediaEventHandler);
450475

451476
// the following event is only added when the card configuration editor is created.
452477
// always remove the following events, as isCardInEditPreview() can sometimes
@@ -658,6 +683,86 @@ export class Card extends LitElement {
658683
}
659684

660685

686+
/**
687+
* Handles the `FILTER_SECTION_MEDIA` event.
688+
* This will show the specified section, and apply the specified filter criteria
689+
* passed in the event arguments.
690+
*
691+
* @param ev Event definition and arguments.
692+
*/
693+
protected onFilterSectionMediaEventHandler = (ev: Event) => {
694+
695+
// map event arguments.
696+
const evArgs = (ev as CustomEvent).detail as FilterSectionMediaEventArgs;
697+
698+
// validate section id.
699+
const enumValues: string[] = Object.values(Section);
700+
if (!enumValues.includes(evArgs.section || "")) {
701+
debuglog("%onFilterSectionMediaEventHandler - Ignoring Filter request; section is not a valid Section enum value:\n%s",
702+
"color:red",
703+
JSON.stringify(evArgs, null, 2),
704+
);
705+
}
706+
707+
// is section activated? if so, then select it.
708+
if (this.config.sections?.includes(evArgs.section as Section)) {
709+
710+
// show the search section.
711+
this.section = evArgs.section as Section;
712+
this.store.section = this.section;
713+
714+
// wait just a bit before executing the search.
715+
setTimeout(() => {
716+
717+
if (debuglog.enabled) {
718+
debuglog("onFilterSectionMediaEventHandler - executing filter:\n%s",
719+
JSON.stringify(evArgs, null, 2),
720+
);
721+
}
722+
723+
// reference the section browser.
724+
let browserBase: FavBrowserBase;
725+
if (evArgs.section == Section.ALBUM_FAVORITES) {
726+
browserBase = this.elmAlbumFavBrowserForm;
727+
} else if (evArgs.section == Section.ARTIST_FAVORITES) {
728+
browserBase = this.elmArtistFavBrowserForm;
729+
} else if (evArgs.section == Section.AUDIOBOOK_FAVORITES) {
730+
browserBase = this.elmAudiobookFavBrowserForm;
731+
} else if (evArgs.section == Section.DEVICES) {
732+
browserBase = this.elmDeviceBrowserForm;
733+
} else if (evArgs.section == Section.EPISODE_FAVORITES) {
734+
browserBase = this.elmEpisodeFavBrowserForm;
735+
} else if (evArgs.section == Section.PLAYLIST_FAVORITES) {
736+
browserBase = this.elmPlaylistFavBrowserForm;
737+
} else if (evArgs.section == Section.RECENTS) {
738+
browserBase = this.elmRecentBrowserForm;
739+
} else if (evArgs.section == Section.SHOW_FAVORITES) {
740+
browserBase = this.elmShowFavBrowserForm;
741+
} else if (evArgs.section == Section.TRACK_FAVORITES) {
742+
browserBase = this.elmTrackFavBrowserForm;
743+
} else if (evArgs.section == Section.USERPRESETS) {
744+
browserBase = this.elmUserPresetBrowserForm;
745+
} else {
746+
return;
747+
}
748+
749+
// execute the filter.
750+
browserBase.filterSectionMedia(evArgs);
751+
//super.requestUpdate();
752+
753+
}, 50);
754+
755+
} else {
756+
757+
// section is not activated; cannot search.
758+
debuglog("%onFilterSectionMediaEventHandler - Filter section is not enabled; ignoring filter request:\n%s",
759+
"color:red",
760+
JSON.stringify(evArgs, null, 2),
761+
);
762+
}
763+
}
764+
765+
661766
/**
662767
* Handles the `SEARCH_MEDIA` event.
663768
* This will execute a search on the specified criteria passed in the event arguments.

src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { css } from 'lit';
22

33
/** current version of the card. */
4-
export const CARD_VERSION = '1.0.23';
4+
export const CARD_VERSION = '1.0.24';
55

66
/** SpotifyPlus integration domain identifier. */
77
export const DOMAIN_SPOTIFYPLUS = 'spotifyplus';

src/events/filter-section-media.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { DOMAIN_SPOTIFYPLUS } from '../constants';
2+
3+
/**
4+
* Uniquely identifies the event.
5+
* */
6+
export const FILTER_SECTION_MEDIA = DOMAIN_SPOTIFYPLUS + '-card-filter-section-media';
7+
8+
9+
/**
10+
* Event arguments.
11+
*/
12+
export class FilterSectionMediaEventArgs {
13+
14+
/**
15+
* Section type to filter.
16+
*/
17+
public section: string;
18+
19+
/**
20+
* Filter criteria.
21+
*/
22+
public filterCriteria: string;
23+
24+
25+
/**
26+
* Initializes a new instance of the class.
27+
*
28+
* @param section Section to be filtered.
29+
* @param filterCriteria Filter criteria that will be applied to the specified filter section.
30+
*/
31+
constructor(
32+
section: string | undefined | null,
33+
filterCriteria: string | undefined | null = null,
34+
) {
35+
this.section = section || "";
36+
this.filterCriteria = filterCriteria || "";
37+
}
38+
}
39+
40+
41+
/**
42+
* Event constructor.
43+
*
44+
* @param section Section to be filtered.
45+
* @param filterCriteria Filter criteria that will be applied to the specified filter section.
46+
*/
47+
export function FilterSectionMediaEvent(
48+
section: string | undefined | null,
49+
filterCriteria: string | undefined | null,
50+
) {
51+
52+
const args = new FilterSectionMediaEventArgs(section);
53+
args.filterCriteria = (filterCriteria || "").trim();
54+
55+
return new CustomEvent(FILTER_SECTION_MEDIA, {
56+
bubbles: true,
57+
composed: true,
58+
detail: args,
59+
});
60+
}

src/sections/fav-browser-base.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { SpotifyPlusService } from '../services/spotifyplus-service';
1717
import { storageService } from '../decorators/storage';
1818
import { truncateMediaList } from '../utils/media-browser-utils';
1919
import { isCardInEditPreview, loadHaFormLazyControls } from '../utils/utils';
20+
import { FilterSectionMediaEventArgs } from '../events/filter-section-media';
2021
import { ProgressEndedEvent } from '../events/progress-ended';
2122
import { ProgressStartedEvent } from '../events/progress-started';
2223
import { DOMAIN_SPOTIFYPLUS } from '../constants';
@@ -326,6 +327,27 @@ export class FavBrowserBase extends LitElement {
326327
}
327328

328329

330+
/**
331+
* Execute filter based on passed arguments.
332+
*/
333+
public filterSectionMedia(args: FilterSectionMediaEventArgs): void {
334+
335+
if (debuglog.enabled) {
336+
debuglog("filterSectionMedia - filtering section media:\n%s",
337+
JSON.stringify(args, null, 2),
338+
);
339+
}
340+
341+
// apply filter criteria.
342+
this.filterCriteria = args.filterCriteria;
343+
//this.requestUpdate();
344+
345+
// execute the search.
346+
//this.updateMediaList(this.player);
347+
348+
}
349+
350+
329351
/**
330352
* Loads values from persistant storage.
331353
*/

src/sections/userpreset-browser.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// debug logging.
2+
import Debug from 'debug/src/browser.js';
3+
import { DEBUG_APP_NAME } from '../constants';
4+
const debuglog = Debug(DEBUG_APP_NAME + ":userpreset-browser");
5+
16
// lovelace card imports.
27
import { html, TemplateResult } from 'lit';
38
import { customElement } from 'lit/decorators.js';
@@ -13,6 +18,7 @@ import { formatTitleInfo } from '../utils/media-browser-utils';
1318
import { getUtcNowTimestamp } from '../utils/utils';
1419
import { IUserPreset } from '../types/spotifyplus/user-preset';
1520
import { CategoryDisplayEvent } from '../events/category-display';
21+
import { FilterSectionMediaEvent } from '../events/filter-section-media';
1622
import { ALERT_ERROR_SPOTIFY_PREMIUM_REQUIRED } from '../constants';
1723

1824

@@ -104,12 +110,33 @@ export class UserPresetBrowser extends FavBrowserBase {
104110
*/
105111
protected override onItemSelected(evArgs: CustomEvent) {
106112

113+
if (debuglog.enabled) {
114+
debuglog("onItemSelected - media item selected:\n%s",
115+
JSON.stringify(evArgs.detail, null, 2),
116+
);
117+
}
118+
107119
// is this a recommendations type?
108120
if (evArgs.detail.type == "recommendations") {
109121

110122
const mediaItem = evArgs.detail as IUserPreset;
111123
this.PlayTrackRecommendations(mediaItem);
112124

125+
} else if (evArgs.detail.type == "filtersection") {
126+
127+
const preset = evArgs.detail as IUserPreset;
128+
129+
// validate filter section name.
130+
const enumValues: string[] = Object.values(Section);
131+
if (!enumValues.includes(preset.filter_section || "")) {
132+
//if (Object.values(Section) as string[]).includes(preset.filter_section || "") {
133+
this.alertErrorSet("Preset filter_section \"" + preset.filter_section + "\" is not a valid section identifier.");
134+
return;
135+
}
136+
137+
// fire event.
138+
this.dispatchEvent(FilterSectionMediaEvent(preset.filter_section, preset.filter_criteria));
139+
113140
} else if (evArgs.detail.type == "category") {
114141

115142
const preset = evArgs.detail as IUserPreset;

src/types/spotifyplus/user-preset.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ export interface IUserPreset {
5959
*/
6060
shuffle?: boolean | null;
6161

62+
/**
63+
* Filter criteria that will be applied to the specified filter section.
64+
* This property should only be populated for type = "filtersection".
65+
*/
66+
filter_criteria?: string | null;
67+
68+
/**
69+
* Section to be filtered.
70+
* This property should only be populated for type = "filtersection".
71+
*/
72+
filter_section?: string | null;
73+
6274
}
6375

6476

0 commit comments

Comments
 (0)