diff --git a/dist/spotify-playlist-card-hc.js b/dist/spotify-playlist-card-hc.js index ff4a790..70e6182 100644 --- a/dist/spotify-playlist-card-hc.js +++ b/dist/spotify-playlist-card-hc.js @@ -1,12 +1,17 @@ -// Examples: +// For learning purposes, I studied the following cards: +// https://github.com/robmarkoski/ha-clockwork-card/blob/master/clockwork-card.js + + +// Examples of using an HA entity's attributes: +// This example looks into HA entity (sensor.spotifyplaylist), the attribute called 'Unorganized', and sub attribute 'name' // const entityId = this.config.entity; // const playlist = hass.states[entityId].attributes; -// ${playlist['Unorganized']['name']}
-// ${playlist['Unorganized']['image']}
-// ${playlist['Unorganized']['uri']}
+// ${playlist['Unorganized']['name']} +// ${playlist['Unorganized']['image']} +// ${playlist['Unorganized']['uri']} -class SpotifyPlaylistCardHC extends HTMLElement { +class SpotifyPlaylistCard extends HTMLElement { constructor() { super(); @@ -17,30 +22,43 @@ class SpotifyPlaylistCardHC extends HTMLElement { const root = this.shadowRoot; if (root.lastChild) root.removeChild(root.lastChild); + // Contains values of this Lovelace card configuration const cardConfig = Object.assign({}, config); + // Default values of config options. Uses ternary statements + const columns = config.columns ? config.columns : 3; + const gradientLevel = config.gradient_level ? config.gradient_level : 0.8; + const gridGap = config.grid_gap ? config.grid_gap : '8px'; + // changes opacity to 0 to hide playlist title + const showPlaylistTitles = config.show_playlist_titles ? 1 : 0; + + + if (!config.entity) { + throw new Error('Please define the name of the Spotify Playlist sensor.'); + } + + const card = document.createElement('div'); const content = document.createElement('div'); const style = document.createElement('style'); -// Ideas: if 'column' and/or 'row' are not defined in options, then use this CSS to automatically organize playlists: -// 'grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));' + // Ideas: if 'column' and/or 'row' are not defined in options, then use this CSS to automatically organize playlists: + // 'grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));' style.textContent = ` .outercontainer { margin:auto; display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-gap: 8px; + grid-template-columns: repeat(${columns}, 1fr); + grid-gap: ${gridGap}; + } .grid-item { position: relative; - flex-basis: calc(33.333%); background-repeat: no-repeat; background-size: cover; background-position: center center; border-radius: 3px; - } .grid-item::before { @@ -49,15 +67,11 @@ class SpotifyPlaylistCardHC extends HTMLElement { padding-top: 100%; } - body { - padding:0; - margin:0; - } - .content { border-radius:0px 0px 3px 3px; position: absolute; bottom: 0; + opacity: ${showPlaylistTitles}; width:100%; padding: 20px 10px 10px 10px; border: 0; @@ -67,9 +81,9 @@ class SpotifyPlaylistCardHC extends HTMLElement { overflow: hidden; text-overflow: ellipsis; background: rgb(0,0,0); - background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,0.86) 30%, rgba(0,0,0,0.86) 100%); - background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,0.86) 30%,rgba(0,0,0,0.86) 100%); - background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,0.86) 30%,rgba(0,0,0,0.86) 100%); + background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,${gradientLevel}) 30%, rgba(0,0,0,${gradientLevel}) 100%); + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,${gradientLevel}) 30%,rgba(0,0,0,${gradientLevel}) 100%); + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,${gradientLevel}) 30%,rgba(0,0,0,${gradientLevel}) 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#db000000',GradientType=0 ); } `; @@ -86,43 +100,89 @@ class SpotifyPlaylistCardHC extends HTMLElement { card.appendChild(content); card.appendChild(style); root.appendChild(card); - this._config = cardConfig; + this.config = cardConfig; } set hass(hass) { - const config = this._config; + const config = this.config; const root = this.shadowRoot; const card = root.lastChild; + let card_content = ``; + let playlist = {}; this.myhass = hass; - let card_content = ` - -
- -
Spotify List 1
-
Another List Here
-
Cool List
-
Spotify Hits
-
Another Hit List
-
Best Songs Here
-
Just Another Long Playlist Name Here
-
Shot One
-
Playlist Name Nine
- -
- `; - - - + try { + playlist = hass.states[config.entity].attributes; + // Beginning of CSS grid + card_content += ` +
+ `; + + for (let entry in playlist) { + // Sanity check that attributes in Spotify Playlist sensor are valid playlists + if (entry !== "friendly_name" && entry !== "icon" && entry !== "homebridge_hidden") { + card_content += ` +
+
${playlist[entry]['name']}
+
+ `; + } + } + // End of CSS grid + card_content += `
`; + } + catch(err) { + throw new Error(`Error detected: ${err}`); + } root.lastChild.hass = hass; root.getElementById('content').innerHTML = card_content; + + // Add a click event to any CSS objects that has an ID: playlist01, playlist02, etc. + try { + // need to redefine 'playlist' as it was walked through in entirety in prev for loop. + playlist = hass.states[config.entity].attributes; + const mediaPlayer = config.media_player ? config.media_player : "default"; + const speakerName = config.speaker_name ? config.speaker_name : "media_player.spotify"; + const shuffleBoolean = config.shuffle ? config.shuffle : true; + const shuffleParameters = { "entity_id": speakerName, "shuffle": shuffleBoolean }; + let playlistParameters = {}; + + // Learn about: + // hass.callService: https://developers.home-assistant.io/docs/en/frontend_data.html#hassuser + for (let entry in playlist) { + if (entry !== "friendly_name" && entry !== "icon" && entry !== "homebridge_hidden") { + card.querySelector(`#playlist${playlist[entry]['id']}`).addEventListener('click', event => { + if (mediaPlayer == "alexa") { + this.myhass.callService('media_player', 'shuffle_set', shuffleParameters); + playlistParameters = {"entity_id": speakerName, "media_content_type": "SPOTIFY", "media_content_id": `${playlist[entry]['name']}`}; + this.myhass.callService('media_player', 'play_media', playlistParameters); + } + else if (mediaPlayer == "spotcast") { + this.myhass.callService('media_player', 'shuffle_set', shuffleParameters); + playlistParameters = {"entity_id": speakerName, "uri": `${playlist[entry]['uri']}`, "random_song": shuffleBoolean }; + this.myhass.callService('spotcast', 'start', playlistParameters); + } + else { + this.myhass.callService('media_player', 'shuffle_set', shuffleParameters); + playlistParameters = {"entity_id": speakerName, "media_content_type": "playlist", "media_content_id": `${playlist[entry]['uri']}`}; + this.myhass.callService('media_player', 'play_media', playlistParameters); + } + }); + } + } + } + catch(err) { + throw new Error(`Error detected: ${err}`); + } + + } getCardSize() { return 1; } } -customElements.define('spotify-playlist-card-hc', SpotifyPlaylistCardHC); \ No newline at end of file +customElements.define('spotify-playlist-card', SpotifyPlaylistCard); \ No newline at end of file diff --git a/dist/spotify-playlist-card.js b/dist/spotify-playlist-card.js index 12fb515..70e6182 100644 --- a/dist/spotify-playlist-card.js +++ b/dist/spotify-playlist-card.js @@ -1,9 +1,14 @@ -// Examples: +// For learning purposes, I studied the following cards: +// https://github.com/robmarkoski/ha-clockwork-card/blob/master/clockwork-card.js + + +// Examples of using an HA entity's attributes: +// This example looks into HA entity (sensor.spotifyplaylist), the attribute called 'Unorganized', and sub attribute 'name' // const entityId = this.config.entity; // const playlist = hass.states[entityId].attributes; -// ${playlist['Unorganized']['name']}
-// ${playlist['Unorganized']['image']}
-// ${playlist['Unorganized']['uri']}
+// ${playlist['Unorganized']['name']} +// ${playlist['Unorganized']['image']} +// ${playlist['Unorganized']['uri']} class SpotifyPlaylistCard extends HTMLElement { @@ -14,113 +19,76 @@ class SpotifyPlaylistCard extends HTMLElement { } setConfig(config) { - if (!config.entity) { - throw new Error('Please define an entity.'); - } const root = this.shadowRoot; if (root.lastChild) root.removeChild(root.lastChild); + // Contains values of this Lovelace card configuration const cardConfig = Object.assign({}, config); - if (!cardConfig.title) { - cardConfig.title = `Playlists`; - } - - if (!config.size) { - config.size = `15vmin`; - } - if (!config.columns) { - config.columns = 3; - } + // Default values of config options. Uses ternary statements + const columns = config.columns ? config.columns : 3; + const gradientLevel = config.gradient_level ? config.gradient_level : 0.8; + const gridGap = config.grid_gap ? config.grid_gap : '8px'; + // changes opacity to 0 to hide playlist title + const showPlaylistTitles = config.show_playlist_titles ? 1 : 0; - if (!config.media_player) { - config.media_player = `default`; - } - if (!config.speaker_name) { - config.speaker_name = `speaker name`; + if (!config.entity) { + throw new Error('Please define the name of the Spotify Playlist sensor.'); } + const card = document.createElement('div'); const content = document.createElement('div'); const style = document.createElement('style'); - style.textContent = ` + // Ideas: if 'column' and/or 'row' are not defined in options, then use this CSS to automatically organize playlists: + // 'grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));' - button { - border: 0px; - padding: 0; - color: #FFFFFF; - border: 0; - width:100% - font-size: 14px; - margin: 0; - background-color: rgba(0,0,0,0.0); + style.textContent = ` + .outercontainer { + margin:auto; + display: grid; + grid-template-columns: repeat(${columns}, 1fr); + grid-gap: ${gridGap}; } - button:hover { + .grid-item { + position: relative; + background-repeat: no-repeat; + background-size: cover; + background-position: center center; + border-radius: 3px; } - button img { - display: block; - width: 100%; - border: 2px; - border-radius: 4px; - `; - - style.textContent += ` height: `; - style.textContent += config.size; - style.textContent += `; - width: `; - style.textContent += config.size; - style.textContent += `; - }` - - style.textContent += ` - .grid-container { - justify-content: center; - justify-items: center; - align-items: center; - display: grid; - border: 0; - grid-gap: 8px; - grid-template-columns: auto`; - var cssColumns = ' auto'.repeat(config.columns); - style.textContent += cssColumns; - style.textContent += `;} - - .grid-item { - border: 0; - padding: 0; - position: relative; + .grid-item::before { + content: ''; + display: block; + padding-top: 100%; } - .grid-item-text { + + .content { + border-radius:0px 0px 3px 3px; + position: absolute; + bottom: 0; + opacity: ${showPlaylistTitles}; + width:100%; + padding: 20px 10px 10px 10px; border: 0; - padding: 18px 10px 10px 10px; text-align:left; - position: absolute; - bottom: 0; - width: 100%; + color: rgba(255,255,255,1); box-sizing:border-box; - color: white; - background: rgb(0,0,0); - background: linear-gradient(360deg, rgba(0,0,0,1) 0%, rgba(0,0,0,0.5) 69%, rgba(0,0,0,0) 100%); overflow: hidden; text-overflow: ellipsis; - border-radius: 4px; - } - .grid-title { - grid-row: 1 / span 4; - text-align: center; - vertical-align: text-top; - writing-mode: vertical-rl; - } - .grid-item ha-icon { - -ms-transform: rotate(90deg); /* IE 9 */ - -webkit-transform: rotate(90deg); /* Safari */ - transform: rotate(90deg); - } + background: rgb(0,0,0); + background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,${gradientLevel}) 30%, rgba(0,0,0,${gradientLevel}) 100%); + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,${gradientLevel}) 30%,rgba(0,0,0,${gradientLevel}) 100%); + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,${gradientLevel}) 30%,rgba(0,0,0,${gradientLevel}) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#db000000',GradientType=0 ); + } `; + + content.innerHTML = `
@@ -132,85 +100,85 @@ class SpotifyPlaylistCard extends HTMLElement { card.appendChild(content); card.appendChild(style); root.appendChild(card); - this._config = cardConfig; + this.config = cardConfig; } set hass(hass) { - const config = this._config; + const config = this.config; const root = this.shadowRoot; const card = root.lastChild; + let card_content = ``; + let playlist = {}; this.myhass = hass; - let card_content = '' - card_content += ` -
-
- - Spotify Playlists -
- `; - - if (hass.states[config.entity]) { - const playlist = hass.states[config.entity].attributes; - let column_count = 0 - + try { + playlist = hass.states[config.entity].attributes; + // Beginning of CSS grid + card_content += ` +
+ `; + for (let entry in playlist) { + // Sanity check that attributes in Spotify Playlist sensor are valid playlists if (entry !== "friendly_name" && entry !== "icon" && entry !== "homebridge_hidden") { - card_content += `
`; - if (config.show_name == true) { - card_content += `
${playlist[entry]['name']}
`; - }; - card_content += `
`; + card_content += ` +
+
${playlist[entry]['name']}
+
+ `; } } - }; - card_content += `
`; -// card_content += ` -// `; - + // End of CSS grid + card_content += `
`; + } + catch(err) { + throw new Error(`Error detected: ${err}`); + } root.lastChild.hass = hass; root.getElementById('content').innerHTML = card_content; - if (hass.states[config.entity]) { - const playlist = hass.states[config.entity].attributes; - const media_player = config.media_player; - const speaker_name = config.speaker_name; + // Add a click event to any CSS objects that has an ID: playlist01, playlist02, etc. + try { + // need to redefine 'playlist' as it was walked through in entirety in prev for loop. + playlist = hass.states[config.entity].attributes; + const mediaPlayer = config.media_player ? config.media_player : "default"; + const speakerName = config.speaker_name ? config.speaker_name : "media_player.spotify"; + const shuffleBoolean = config.shuffle ? config.shuffle : true; + const shuffleParameters = { "entity_id": speakerName, "shuffle": shuffleBoolean }; + let playlistParameters = {}; + + // Learn about: + // hass.callService: https://developers.home-assistant.io/docs/en/frontend_data.html#hassuser for (let entry in playlist) { if (entry !== "friendly_name" && entry !== "icon" && entry !== "homebridge_hidden") { card.querySelector(`#playlist${playlist[entry]['id']}`).addEventListener('click', event => { - if (media_player == "echo") { - const myPlaylist = {"entity_id": speaker_name, "media_content_type": "playlist", "media_content_id": `${playlist[entry]['uri']}`}; - this.myhass.callService('media_player', 'play_media', myPlaylist); + if (mediaPlayer == "alexa") { + this.myhass.callService('media_player', 'shuffle_set', shuffleParameters); + playlistParameters = {"entity_id": speakerName, "media_content_type": "SPOTIFY", "media_content_id": `${playlist[entry]['name']}`}; + this.myhass.callService('media_player', 'play_media', playlistParameters); } - else if (media_player == "spotcast") { - const spotcastPlaylist = {"device_name": speaker_name, "uri": `${playlist[entry]['uri']}`}; - this.myhass.callService('spotcast', 'start', spotcastPlaylist); + else if (mediaPlayer == "spotcast") { + this.myhass.callService('media_player', 'shuffle_set', shuffleParameters); + playlistParameters = {"entity_id": speakerName, "uri": `${playlist[entry]['uri']}`, "random_song": shuffleBoolean }; + this.myhass.callService('spotcast', 'start', playlistParameters); + } + else { + this.myhass.callService('media_player', 'shuffle_set', shuffleParameters); + playlistParameters = {"entity_id": speakerName, "media_content_type": "playlist", "media_content_id": `${playlist[entry]['uri']}`}; + this.myhass.callService('media_player', 'play_media', playlistParameters); } }); } } } + catch(err) { + throw new Error(`Error detected: ${err}`); + } + + } getCardSize() { return 1;