1- import StreamDeck , {
2- action ,
3- SendToPluginEvent ,
4- WillAppearEvent
1+ import {
2+ action
53} from '@elgato/streamdeck'
64
75import {
86 Button
97} from './button.js'
108
119import constants from '../library/constants.js'
10+ import images from '../library/images.js'
1211import wrapper from './../library/wrapper.js'
13- import connector from '../library/connector.js'
1412
1513@action ( { UUID : 'com.ntanis.essentials-for-spotify.add-to-playlist-button' } )
1614export default class AddToPlaylistButton extends Button {
1715 static readonly STATABLE = true
1816
17+ #cachedPlaylist: {
18+ [ context : string ] : any
19+ } = { }
20+
1921 constructor ( ) {
2022 super ( )
2123 this . setStatelessImage ( 'images/states/add-to-playlist-unknown' )
@@ -30,12 +32,11 @@ export default class AddToPlaylistButton extends Button {
3032 if ( ! song || pending ) {
3133 this . clearMarquee ( context )
3234 await this . setTitle ( context , '' )
33- await this . setImage ( context , 'images/states/add-to-playlist-unknown' )
35+ await this . #updateImage ( context )
3436 this . setUnpressable ( context , true )
3537 } else {
36- await this . setImage ( context , 'images/states/add-to-playlist' )
3738 this . setUnpressable ( context , false )
38- await this . #updateDisplay( context , song )
39+ await this . #updateDisplay( context )
3940 }
4041
4142 resolve ( true )
@@ -44,94 +45,90 @@ export default class AddToPlaylistButton extends Button {
4445 await Promise . allSettled ( promises )
4546 }
4647
47- async #updateDisplay( context : string , song : any = wrapper . song ) {
48- const show = this . settings [ context ] . show || [ 'playlist' ]
48+ #processImagePlus( iconDataUrl : string ) : string {
49+ const iconSize = 120
50+ const badgeSize = 36
51+ const badgeX = iconSize - badgeSize - 6
52+ const badgeY = iconSize - badgeSize - 6
53+
54+ const svg = `
55+ <svg width="${ iconSize } " height="${ iconSize } " viewBox="0 0 ${ iconSize } ${ iconSize } " xmlns="http://www.w3.org/2000/svg">
56+
57+ <defs>
58+ <pattern id="iconPattern" patternUnits="userSpaceOnUse" width="${ iconSize } " height="${ iconSize } ">
59+ <image href="${ iconDataUrl } " x="0" y="0" width="${ iconSize } " height="${ iconSize } "/>
60+ </pattern>
61+ </defs>
62+
63+ <rect width="${ iconSize } " height="${ iconSize } " fill="url(#iconPattern)"/>
64+ <circle cx="${ badgeX + badgeSize / 2 } " cy="${ badgeY + badgeSize / 2 } " r="${ badgeSize / 2 } " fill="#1db954" stroke="#191414" stroke-width="2"/>
65+ <line x1="${ badgeX + badgeSize / 2 } " y1="${ badgeY + 9 } " x2="${ badgeX + badgeSize / 2 } " y2="${ badgeY + badgeSize - 9 } " stroke="#191414" stroke-width="3" stroke-linecap="round"/>
66+ <line x1="${ badgeX + 9 } " y1="${ badgeY + badgeSize / 2 } " x2="${ badgeX + badgeSize - 9 } " y2="${ badgeY + badgeSize / 2 } " stroke="#191414" stroke-width="3" stroke-linecap="round"/>
67+
68+ </svg>
69+ `
70+
71+ return `data:image/svg+xml;base64,${ Buffer . from ( svg ) . toString ( 'base64' ) } `
72+ }
73+
74+ async #updateImage( context : string ) {
75+ if ( ! this . #cachedPlaylist[ context ] ) {
76+ await this . setImage ( context , 'images/states/add-to-playlist-unknown' )
77+ return
78+ }
79+
80+ if ( ! images . isItemCached ( this . #cachedPlaylist[ context ] ) )
81+ await this . setImage ( context , 'images/states/pending' )
82+
83+ const image = await images . getForItem ( this . #cachedPlaylist[ context ] )
84+
85+ if ( image )
86+ await this . setImage ( context , this . #processImagePlus( `data:image/jpeg;base64,${ image } ` ) )
87+ else
88+ await this . setImage ( context , 'images/states/add-to-playlist' )
89+ }
90+
91+ async #updateDisplay( context : string ) {
92+ const show = this . settings [ context ] . show || [ 'title' ]
4993 const data : any = [ ]
5094
5195 let needsRestart = ! this . marquees [ context ]
5296
53- for ( const item of show )
54- if ( item === 'playlist' && this . settings [ context ] . playlist_name )
55- data . push ( {
56- key : 'playlist' ,
57- value : this . settings [ context ] . playlist_name
58- } )
59- else if ( item === 'name' && song ?. item ?. name ) {
60- data . push ( {
61- key : 'name' ,
62- value : song . item . name
63- } )
64-
65- if ( this . marquees [ context ] ?. entries ?. name && this . marquees [ context ] . entries . name . original !== song . item . name )
66- this . updateMarqueeEntry ( context , 'name' , song . item . name )
67- }
97+ if ( show . includes ( 'title' ) && this . #cachedPlaylist[ context ] ?. title )
98+ data . push ( {
99+ key : 'title' ,
100+ value : this . #cachedPlaylist[ context ] . title
101+ } )
68102
69103 if ( data . length === 0 ) {
70104 this . clearMarquee ( context )
71105 await this . setTitle ( context , '' )
72106 } else if ( needsRestart || ( ! this . marquees [ context ] ) )
73107 await this . marqueeTitle ( 'add-to-playlist' , data , context )
74- }
75-
76- async #updatePlaylists( contexts = this . contexts ) {
77- const items : any = [ ]
78-
79- if ( connector . set )
80- try {
81- let page = 1
82-
83- while ( true ) {
84- const playlistsResponse = await wrapper . getUserPlaylists ( page )
85-
86- if ( playlistsResponse && playlistsResponse . status === constants . WRAPPER_RESPONSE_SUCCESS ) {
87- for ( const playlist of playlistsResponse . items )
88- if ( playlist ) {
89- const canAddTracks = playlist . type === 'collection' || playlist . owner ?. id === wrapper . user ?. id || playlist . collaborative === true
90108
91- if ( canAddTracks )
92- items . push ( {
93- value : playlist . id ,
94- label : playlist . name
95- } )
96- }
97-
98- if ( ( page * constants . WRAPPER_ITEMS_PER_PAGE ) >= playlistsResponse . total )
99- break
100-
101- page ++
102- } else
103- break
104- }
105- } catch ( e ) { }
106-
107- for ( const context of contexts ) {
108- await StreamDeck . ui . sendToPropertyInspector ( {
109- event : 'getPlaylists' ,
110- items
111- } ) . catch ( ( ) => { } )
109+ await this . #updateImage( context )
110+ }
112111
113- if ( connector . set && this . settings [ context ] . playlist_id && items . length > 0 ) {
114- const playlist = items . find ( ( p : any ) => p ?. value === this . settings [ context ] . playlist_id )
112+ async #resolvePlaylist( context : string ) {
113+ const spotify_url = this . settings [ context ] . spotify_url
114+ const badUrl = ! / ^ h t t p s ? : \/ \/ o p e n \. s p o t i f y \. c o m \/ (?: i n t l - [ a - z ] { 2 } \/ ) ? p l a y l i s t \/ [ A - Z a - z 0 - 9 ] { 22 } (?: \/ ) ? (?: \? .* ) ? $ / . test ( spotify_url )
115115
116- if ( playlist ) {
117- const oldPlaylistName = this . settings [ context ] . playlist_name
116+ if ( ( ! spotify_url ) || badUrl ) {
117+ this . #cachedPlaylist[ context ] = null
118+ return
119+ }
118120
119- await this . setSettings ( context , {
120- playlist_name : playlist . label
121- } )
121+ if ( this . #cachedPlaylist[ context ] ?. url === spotify_url )
122+ return
122123
123- if ( oldPlaylistName !== playlist . label )
124- this . updateMarqueeEntry ( context , 'playlist' , playlist . label )
125- }
126- }
127- }
124+ this . #cachedPlaylist[ context ] = await wrapper . getInformationOnUrl ( spotify_url )
128125 }
129126
130127 async invokeWrapperAction ( context : string , type : symbol ) {
131128 if ( type === Button . TYPES . RELEASED )
132129 return
133130
134- if ( ! this . settings [ context ] . playlist_id )
131+ if ( ! this . #cachedPlaylist [ context ] ?. id )
135132 return constants . WRAPPER_RESPONSE_NOT_AVAILABLE
136133
137134 const currentTrack = await wrapper . getCurrentTrack ( )
@@ -140,62 +137,53 @@ export default class AddToPlaylistButton extends Button {
140137 if ( ! wrapper . song ?. item ?. id )
141138 return constants . WRAPPER_RESPONSE_NOT_AVAILABLE
142139
143- const response = await wrapper . addSongToPlaylist ( this . settings [ context ] . playlist_id , wrapper . song . item . uri )
140+ const response = await wrapper . addSongToPlaylist ( this . #cachedPlaylist [ context ] . id , wrapper . song . item . uri )
144141
145142 if ( response === constants . WRAPPER_RESPONSE_SUCCESS )
146143 return constants . WRAPPER_RESPONSE_SUCCESS_INDICATIVE
147144 else
148145 return response
149146 }
150147
151- const response = await wrapper . addSongToPlaylist ( this . settings [ context ] . playlist_id , currentTrack . uri )
148+ const response = await wrapper . addSongToPlaylist ( this . #cachedPlaylist [ context ] . id , currentTrack . uri )
152149
153150 if ( response === constants . WRAPPER_RESPONSE_SUCCESS )
154151 return constants . WRAPPER_RESPONSE_SUCCESS_INDICATIVE
155152 else
156153 return response
157154 }
158155
159- async onSendToPlugin ( ev : SendToPluginEvent < any , any > ) : Promise < void > {
160- if ( ev . payload ?. event === 'getPlaylists' )
161- await this . #updatePlaylists( [ ev . action . id ] )
162- }
163-
164156 async onSettingsUpdated ( context : string , oldSettings : any ) {
165157 await super . onSettingsUpdated ( context , oldSettings )
166158
167159 if ( ! this . settings [ context ] . show )
168160 await this . setSettings ( context , {
169- show : [ 'playlist ' ]
161+ show : [ 'title ' ]
170162 } )
171163
172- if ( oldSettings . playlist_id !== this . settings [ context ] . playlist_id )
173- await this . #updatePlaylists( [ context ] )
174-
164+ const urlChanged = oldSettings . spotify_url !== this . settings [ context ] . spotify_url
175165 const showChanged = oldSettings . show ?. length !== this . settings [ context ] . show ?. length || ( oldSettings . show && this . settings [ context ] . show && ( ! oldSettings . show . every ( ( value : any , index : number ) => value === this . settings [ context ] . show [ index ] ) ) )
176166
177- if ( showChanged ) {
167+ if ( urlChanged ) {
178168 this . clearMarquee ( context )
169+ await this . #resolvePlaylist( context )
170+ }
179171
180- if ( wrapper . song )
181- await this . #updateDisplay( context , wrapper . song )
182- else
183- await this . #updateDisplay( context , null )
184- } else if ( oldSettings . playlist_id !== this . settings [ context ] . playlist_id )
185- if ( wrapper . song )
186- await this . #onSongChanged( wrapper . song , false )
187- else
188- await this . #onSongChanged( null , false )
172+ if ( urlChanged || showChanged ) {
173+ if ( showChanged )
174+ this . clearMarquee ( context )
175+
176+ await this . #updateDisplay( context )
177+ }
189178 }
190179
191180 async onStateSettled ( context : string ) {
192181 await super . onStateSettled ( context , true )
193- await this . #updatePlaylists ( [ context ] )
182+ await this . #resolvePlaylist ( context )
194183
195184 if ( wrapper . song ) {
196- await this . setImage ( context , 'images/states/add-to-playlist' )
197185 this . setUnpressable ( context , false )
198- await this . #updateDisplay( context , wrapper . song )
186+ await this . #updateDisplay( context )
199187 } else
200188 await this . #onSongChanged( null , false )
201189 }
0 commit comments