├── .gitignore
├── screen.png
├── images
└── logo.png
├── js
├── spotify.js
├── auth.js
├── config.js
└── script.js
├── README.md
├── LICENSE
├── index.html
└── css
└── style.css
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaaes/albums-availability/HEAD/screen.png
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaaes/albums-availability/HEAD/images/logo.png
--------------------------------------------------------------------------------
/js/spotify.js:
--------------------------------------------------------------------------------
1 | var SpotifyApi = {
2 |
3 | sendRequest: function(url, callback) {
4 |
5 | var accessToken = Config.getValidToken()
6 | if (accessToken == null) {
7 | var error = 'unauthorised';
8 | callback(error);
9 | return;
10 | }
11 |
12 | d3.json(url)
13 | .header('Authorization', 'Bearer ' + accessToken)
14 | .get(callback);
15 | }
16 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Albums Availability Map
2 | -----------------------
3 |
4 | A visualisation for albums and their availability in Spotify.
5 |
6 | This project uses [Spotify Web API](https://developer.spotify.com/web-api/) to get the data
7 | and [Google Charts](https://developers.google.com/chart/) to display them.
8 | There's also a tiny bit of the amazing [D3.js](http://d3js.org/) to help process the data.
9 |
10 | To check an album paste the uri to the search box or drag an album from Spotify client and drop it
11 | somewhere on the page.
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/js/auth.js:
--------------------------------------------------------------------------------
1 | var Auth = {
2 | getAuthUrl: function(siteUrl) {
3 |
4 | var clientId = '54e0e5bde5be499a94ecf7b31c1da2f1';
5 | var redirectUri = encodeURIComponent(siteUrl + '?auth_callback');
6 |
7 | return url = 'https://accounts.spotify.com/authorize?client_id=' + clientId + '&redirect_uri=' + redirectUri + '&response_type=token';
8 | },
9 |
10 | parseResponse: function(url) {
11 | var hash = url.hash;
12 | var response = this._parseHash(hash);
13 | if (response != null) {
14 | Config.setToken(response['access_token']);
15 | Config.setExpiresAt(response['expires_in']);
16 | }
17 | },
18 |
19 | _parseHash: function(hash) {
20 | if (!hash) {
21 | return null;
22 | }
23 |
24 | parsed = hash.replace('#', '')
25 | .split('&')
26 | .map(function(el) {
27 | return el.split('=');
28 | })
29 | .reduce(function(acc, el) {
30 | acc[el[0]] = el[1];
31 | return acc;
32 | }, {});
33 |
34 | return parsed;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/js/config.js:
--------------------------------------------------------------------------------
1 | var Config = {
2 | setLastSearch: function(id) {
3 | localStorage.setItem('lastSearch', id);
4 | window.location.hash = id;
5 | },
6 |
7 | getLastSearch: function() {
8 | return window.location.hash.substring(1) || localStorage.getItem('lastSearch');
9 | },
10 |
11 | setExactSearch: function(useExact) {
12 | localStorage.setItem('exactSearch', mode);
13 | },
14 |
15 | getExactSearch: function() {
16 | return localStorage.getItem('exactSearch') === 'true';
17 | },
18 |
19 | getValidToken: function() {
20 | var expiresAt = parseInt(localStorage.getItem('token_expires_at'), 10);
21 | var token = localStorage.getItem('token');
22 | var now = Date.now();
23 | return !!token && !!expiresAt && expiresAt > now ? token : null;
24 | },
25 |
26 | setToken: function(accessToken) {
27 | localStorage.setItem('token', accessToken);
28 | },
29 |
30 | setExpiresAt: function(expiresIn) {
31 | var now = Date.now();
32 | var expiresAt = now + parseInt(expiresIn, 10) * 1000;
33 | localStorage.setItem('token_expires_at', expiresAt);
34 | }
35 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Kasia Drzyzga
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Album Availability Map
5 |
6 |
7 |
8 |
9 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
Only albums are supported
54 |
55 |
56 |
57 |
You need a Spotify token to be able to get albums data.
58 |
59 |
60 |
61 |
65 |
66 |
71 |
72 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'proxima_nova';
3 | src: url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_light_0.eot');
4 | src: url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_light_0.eot?#iefix') format('embedded-opentype'), url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_light_0.woff') format('woff'), url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_light_0.ttf') format('truetype');
5 | font-weight: 200;
6 | font-style: normal;
7 | }
8 | @font-face {
9 | font-family: 'proxima_nova';
10 | src: url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_thin_0.eot');
11 | src: url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_thin_0.eot?#iefix') format('embedded-opentype'), url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_thin_0.woff') format('woff'), url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_thin_0.ttf') format('truetype');
12 | font-weight: 100;
13 | font-style: normal;
14 | }
15 | @font-face {
16 | font-family: 'proxima_nova';
17 | src: url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_regular_0.eot');
18 | src: url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_regular_0.eot?#iefix') format('embedded-opentype'), url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_regular_0.woff') format('woff'), url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_regular_0.ttf') format('truetype');
19 | font-weight: 400;
20 | font-style: normal;
21 | }
22 | @font-face {
23 | font-family: 'proxima_nova';
24 | src: url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_semibold_0.eot');
25 | src: url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_semibold_0.eot?#iefix') format('embedded-opentype'), url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_semibold_0.woff') format('woff'), url('https://d3nkxvtkt5c8vi.cloudfront.net/3.0.1/fonts/proxima_nova_semibold_0.ttf') format('truetype');
26 | font-weight: 600;
27 | font-style: normal;
28 | }
29 |
30 | body {
31 | background: #121314;
32 | font-weight: 100;
33 | letter-spacing: 1px;
34 | color: #dfe0e6;
35 | }
36 |
37 | h1, h2, h3, h4, h5, h6 {
38 | font-weight: 100;
39 | letter-spacing: 1px;
40 | }
41 |
42 | a {
43 | color: #84bd00;
44 | text-decoration: none;
45 | }
46 | a:hover,
47 | a:focus {
48 | color: #648f00;
49 | text-decoration: underline;
50 | }
51 | a:focus {
52 | outline: thin dotted;
53 | outline: 5px auto -webkit-focus-ring-color;
54 | outline-offset: -2px;
55 | }
56 |
57 | .navbar-logo {
58 | width: 134px;
59 | height: 40px;
60 | display: inline-block;
61 | text-indent: 9999px;
62 | position: relative;
63 | top: -10px;
64 | background-image: url('../images/logo.png');
65 | }
66 |
67 | .navbar-default .navbar-brand:hover,
68 | .navbar-default .navbar-brand:focus {
69 | color: inherit;
70 | }
71 |
72 | .navbar-default {
73 | background-color: rgba(0,0,0,.5);
74 | border-color: #000;
75 | }
76 |
77 | .navbar-default .navbar-brand {
78 | color: inherit;
79 | font-size: 32px;
80 | }
81 |
82 | .navbar .container {
83 | width: 100%;
84 | }
85 |
86 | .navbar-form .form-control {
87 | width: 290px;
88 | }
89 |
90 | .container.regions {
91 | width: 100%;
92 | position: relative;
93 | }
94 |
95 | .regions .info {
96 | background-color: rgba(50,50,50,.3);
97 | box-shadow: 0 0 50px rgba(255,255,255,.1);
98 | }
99 |
100 | .glyphicon-search {
101 | top: 0;
102 | }
103 |
104 | .entity-info {
105 | display: inline-block;
106 | position: absolute;
107 | padding: 1em;
108 | margin: 15px;
109 | z-index: 2;
110 | left: 0;
111 | bottom: 0;
112 | background: rgba(0,0,0,.3)
113 | }
114 |
115 | .entity-info h1,
116 | .entity-info h2 {
117 | margin: 0;
118 | }
119 |
120 | #regions-map {
121 | height: 100%;
122 | width: 80%;
123 | z-index: 1;
124 | position: fixed;
125 | display: none;
126 | }
127 |
128 | #regions-info {
129 | padding-top: 50px;
130 | position: relative;
131 | z-index: 2;
132 | border-right: solid 1px rgba(0,0,0,.1);
133 | }
134 |
135 | .albums-info > div {
136 | padding: 0 10px 5px 10px;
137 | margin-bottom: 5px;
138 | }
139 |
140 | .albums-info > h3 {
141 | font-weight: 200;
142 | padding: 0 10px;
143 | margin-top: 10px;
144 | margin-bottom: 10px;
145 | }
146 |
147 | .albums-info .alert-warning {
148 | font-size: .9em;
149 | padding-top: .5em;
150 | padding-bottom: .5em;
151 | }
152 |
153 | .album-info.searched {
154 | border-left: solid 5px #648f00;
155 | }
156 |
157 | .album-info:hover {
158 | background: rgba(255,255,255,.1);
159 | }
160 |
161 | .albums-info p {
162 | margin: .4em 0;
163 | }
164 |
165 | .albums-info .explicit {
166 | font-size: .7em;
167 | text-transform: uppercase;
168 | border: solid 1px #999;
169 | line-height: .6em;
170 | padding: .1em .1em 0 .3em;
171 | letter-spacing: .2em;
172 | }
173 |
174 | .albums-info .uri {
175 | letter-spacing: .05em;
176 | color: #999;
177 | }
178 |
179 | .albums-info .uri a {
180 | color: inherit;
181 | }
182 |
183 | .albums-info .cover {
184 | box-shadow: 0 0 1px rgba(255,255,255,.3);
185 | width: 64px;
186 | height: 64px;
187 | margin-top: 5px;
188 | display: inline-block;
189 | float: left;
190 | border-radius: 2px
191 | }
192 |
193 | .albums-info ul {
194 | height: 64px;
195 | margin-top: 5px;
196 | display: inline-block;
197 | padding-left: 10px;
198 | list-style: none;
199 | }
200 |
201 | .checkbox {
202 | white-space: nowrap;
203 | }
204 |
205 | .checkbox label {
206 | font-weight: 100;
207 | }
208 |
209 | .albums-info .copyrights {
210 | font-size: .85em;
211 | white-space: nowrap;
212 | overflow: hidden;
213 | text-overflow: ellipsis;
214 | margin: 0;
215 | }
216 |
217 | .albums-info .album-info:hover .copyrights {
218 | white-space: normal;
219 | }
220 |
221 | .albums-only,
222 | .get-token {
223 | display: none;
224 | width: 100%;
225 | position: absolute;
226 | z-index: 2;
227 | top: 50%;
228 | text-align: center;
229 | }
230 |
231 | .invalid-search .albums-only,
232 | .no-token .get-token {
233 | display: block;
234 | }
235 |
236 | @media (min-width: 992px) {
237 | #regions-map {
238 | display: block;
239 | }
240 |
241 | .entity-info {
242 | position: fixed;
243 | }
244 | }
--------------------------------------------------------------------------------
/js/script.js:
--------------------------------------------------------------------------------
1 | google.load("visualization", "1", {packages:["geochart"]});
2 | google.setOnLoadCallback(onGoogleLoaded);
3 |
4 | function onGoogleLoaded() {
5 |
6 | var SITE_URL = 'https://kaaes.github.io/albums-availability';
7 |
8 | var ALL_MARKETS = {
9 | "AD": "Andorra",
10 | "AR": "Argentina",
11 | "AU": "Australia",
12 | "AT": "Austria",
13 | "BE": "Belgium",
14 | "BO": "Bolivia",
15 | "BR": "Brazil",
16 | "BG": "Bulgaria",
17 | "CA": "Canada",
18 | "CL": "Chile",
19 | "CO": "Colombia",
20 | "CR": "Costa Rica",
21 | "CY": "Cyprus",
22 | "CZ": "Czech Republic",
23 | "DK": "Denmark",
24 | "DO": "Dominican Republic",
25 | "EC": "Ecuador",
26 | "SV": "El Salvador",
27 | "EE": "Estonia",
28 | "FI": "Finland",
29 | "FR": "France",
30 | "DE": "Germany",
31 | "GR": "Greece",
32 | "GT": "Guatemala",
33 | "HN": "Honduras",
34 | "HK": "Hong Kong",
35 | "HU": "Hungary",
36 | "ID": "Indonesia",
37 | "IS": "Iceland",
38 | "IE": "Republic of Ireland",
39 | "IT": "Italy",
40 | "JP": "Japan",
41 | "LV": "Latvia",
42 | "LI": "Liechtenstein",
43 | "LT": "Lithuania",
44 | "LU": "Luxembourg",
45 | "MY": "Malaysia",
46 | "MT": "Malta",
47 | "MX": "Mexico",
48 | "MC": "Monaco",
49 | "NL": "Netherlands",
50 | "NZ": "New Zealand",
51 | "NI": "Nicaragua",
52 | "NO": "Norway",
53 | "PA": "Panama",
54 | "PY": "Paraguay",
55 | "PE": "Peru",
56 | "PH": "Philippines",
57 | "PL": "Poland",
58 | "PT": "Portugal",
59 | "ES": "Spain",
60 | "SG": "Singapore",
61 | "SK": "Slovakia",
62 | "SE": "Sweden",
63 | "CH": "Switzerland",
64 | "TW": "Taiwan",
65 | "TR": "Turkey",
66 | "GB": "United Kingdom",
67 | "US": "United States",
68 | "UY": "Uruguay"
69 | };
70 | var SPOTIFY_API = 'https://api.spotify.com/v1';
71 |
72 | var HIGHLIGHT_TIMEOUT = 100;
73 |
74 | var chart;
75 |
76 | var chartContainer = document.querySelector('#regions-map');
77 | var searchForm = document.querySelector('#search-form');
78 | var albumInfoBig = document.querySelector('#data-info');
79 | var infoContainer = document.querySelector('#regions-info');
80 | var exactSearchButton = document.querySelector('#exact-search-button');
81 | var loginButton = document.querySelector('#login-button');
82 |
83 | var albumInfoBigTemplate = document.querySelector('#template-album-info-big').innerHTML;
84 | var albumNotAvailableTemplate = document.querySelector('#template-album-not-available').innerHTML;
85 | var albumNotFoundTemplate = document.querySelector('#template-album-not-found').innerHTML;
86 |
87 | document.body.addEventListener('drop', handleDrop, false);
88 | document.body.addEventListener('dragover', handleDragOver, false);
89 |
90 | var timeoutHandler;
91 | infoContainer.addEventListener('mouseover', handleMouseover);
92 | infoContainer.addEventListener('mouseout', handleMouseout);
93 |
94 | loginButton.addEventListener('click', authenticate);
95 |
96 | exactSearchButton.checked = !Config.getExactSearch();
97 | exactSearchButton.addEventListener('change', handleExactChange);
98 | searchForm.addEventListener('submit', handleFormSubmit);
99 |
100 | function handleFormSubmit(evt) {
101 | evt.preventDefault();
102 | var searchValue = evt.target[0].value;
103 | var uri = parseUri(searchValue);
104 | validateAndPerformSearch(uri);
105 | }
106 |
107 | function handleExactChange(evt) {
108 | Config.setExactSearch(!evt.target.checked);
109 | var lastSearch = Config.getLastSearch();
110 | if (lastSearch) {
111 | performSearch(lastSearch);
112 | }
113 | }
114 |
115 | function handleMouseover(evt) {
116 | clearTimeout(timeoutHandler);
117 | var self = this;
118 | timeoutHandler = setTimeout(function() {
119 | var path = evt.path;
120 | var current = self.dataset.active;
121 | for(var i = 0; i < path.length; i++) {
122 | if (path[i].dataset && path[i].dataset.albumid) {
123 | if (path[i].dataset.albumid != current) {
124 | current = path[i].dataset.albumid;
125 | self.dataset.active = current;
126 | highlight(current);
127 | }
128 | return;
129 | }
130 | }
131 | highlight(null);
132 | self.active = null;
133 | }, HIGHLIGHT_TIMEOUT);
134 | }
135 |
136 | function handleMouseout(evt) {
137 | clearTimeout(timeoutHandler);
138 | var self = this;
139 | timeoutHandler = setTimeout(function() {
140 | self.dataset.active = null;
141 | highlight(null);
142 | }, HIGHLIGHT_TIMEOUT);
143 | }
144 |
145 | function handleDragOver(evt) {
146 | evt.stopPropagation();
147 | evt.preventDefault();
148 | evt.dataTransfer.dropEffect = 'copy';
149 | }
150 |
151 | function handleDrop(evt) {
152 | evt.stopPropagation();
153 | evt.preventDefault();
154 |
155 | var url = evt.dataTransfer.getData('text/plain');
156 | if (!url) return;
157 | if (url.indexOf('open.spotify.com') > -1) {
158 | var parsed = parseUri(url.replace(/https?:\/\/open.spotify.com\//, 'spotify/'), '/');
159 | } else {
160 | var parsed = parseUri(url);
161 | }
162 |
163 | validateAndPerformSearch(parsed);
164 |
165 | return false;
166 | }
167 |
168 | function parseUri(uri, delimiter) {
169 | var segments = uri.split(delimiter || ':');
170 | var id, type;
171 | if (segments[1] === 'user') {
172 | type = segments[3];
173 | id = segments[2] + ':' + segments[4];
174 | } else if (segments[1] === 'album') {
175 | type = segments[1];
176 | id = segments[2];
177 | }
178 | return {type: type, id: id};
179 | }
180 |
181 | function validateAndPerformSearch(parsedUri) {
182 |
183 | if (parsedUri.type == 'album') {
184 | document.body.classList.remove('invalid-search');
185 | performSearch(parsedUri.id);
186 | } else {
187 | document.body.classList.add('invalid-search');
188 | clearChart();
189 | }
190 | }
191 |
192 | function clearChart() {
193 | drawChart([]);
194 | albumInfoBig.innerHTML = '';
195 | infoContainer.innerHTML = '';
196 | }
197 |
198 | function performSearch(id) {
199 | Config.setLastSearch(id);
200 | infoContainer.innerHTML = '';
201 |
202 | SpotifyApi.sendRequest(SPOTIFY_API + '/albums/' + id, function(error, album) {
203 |
204 | if (error && error == 'unauthorised') {
205 | document.body.classList.add('no-token');
206 | clearChart();
207 | return;
208 | }
209 |
210 | fillInfo(album);
211 |
212 | function albumsCallback(result, next) {
213 | if (next) {
214 | getAlbums(next, album.name, albumsCallback, result);
215 | } else {
216 | fillInfoData(result, album);
217 | fillChartData(result);
218 | }
219 | }
220 |
221 | var uri = SPOTIFY_API + '/artists/' + album.artists[0].id + '/albums?album_type=' + album.album_type;
222 |
223 | var markets = [];
224 | var ids = [];
225 | var count = {};
226 | if (album.available_markets.length) {
227 | markets.push(album.available_markets);
228 | ids.push(album.id);
229 | album.available_markets.forEach(function(market) {
230 | count[market] = 1;
231 | });
232 | }
233 |
234 | getAlbums(uri, album.name, albumsCallback, { markets: markets, ids: ids, count: count });
235 | });
236 | }
237 |
238 | function getAlbums(uri, albumName, callback, result) {
239 | SpotifyApi.sendRequest(uri, function(error, albums) {
240 | albums.items.forEach(function(el) {
241 | var elName = el.name.toLowerCase().replace(/[,.;:?!"]/g, '');
242 | var alName = albumName.toLowerCase().replace(/[,.;:?!"]/g, '');
243 | var condition = Config.getExactSearch() ?
244 | el.name == alName : elName.indexOf(alName) == 0 || alName.indexOf(elName) == 0
245 | if (condition && result.ids.indexOf(el.id) == -1) {
246 | result.ids.push(el.id);
247 | result.markets.push(el.available_markets || []);
248 | if (el.available_markets) {
249 | el.available_markets.forEach(function(market) {
250 | result.count[market] = result.count[market] || 0;
251 | result.count[market]++;
252 | });
253 | }
254 | }
255 | });
256 |
257 | callback(result, albums.next);
258 | });
259 | }
260 |
261 | function fillInfo(info) {
262 | var data = {
263 | albumTitle: info.name,
264 | albumType: info.album_type,
265 | artistName: info.artists.map(function(el) { return el.name; }).join(', ')
266 | }
267 | var rendered = Mustache.render(albumInfoBigTemplate, data);
268 | albumInfoBig.innerHTML = rendered;
269 | }
270 |
271 | var currentDataset;
272 |
273 | function highlight(id) {
274 | if (currentDataset) {
275 | var markets = [];
276 | var dataset = currentDataset.map(function(el) {
277 | var row = el.slice(0);
278 | row.spid = el.spid;
279 | if (row.spid && row.spid == id) {
280 | markets.push(row[0])
281 | row[1] = 0.5;
282 | }
283 | return row;
284 | });
285 | drawChart(dataset.filter(function(el) {
286 | return markets.indexOf(el[0]) == -1 || el.spid == id;
287 | }));
288 | }
289 | }
290 |
291 | function getAlbumInfo(name, albums, id) {
292 | var fragment = document.createDocumentFragment();
293 | var title = document.createElement('h3');
294 | title.classList.add("row");
295 | title.classList.add("album-title");
296 | title.innerHTML = name;
297 |
298 | fragment.appendChild(title);
299 |
300 | albums.forEach(function(album) {
301 | var div = document.createElement('div');
302 | div.dataset.albumid = album.id;
303 | div.classList.add("album-info");
304 | div.classList.add("row");
305 |
306 | if (album.id == id) {
307 | div.classList.add("searched");
308 | }
309 |
310 | var explicit = false;
311 | for(var i = 0; i < album.tracks.items.length; i++) {
312 | if (album.tracks.items[i].explicit) {
313 | explicit = true;
314 | break;
315 | }
316 | }
317 |
318 | var copyrights = d3.map(album.copyrights, function(d) { return d.type });
319 |
320 | var html = '';
321 | html += ''
322 | html += '
';
323 | html += '';
324 | html += '';
334 | html += '' + album.available_markets.map(function(el) {
335 | return '' + el + '';
336 | }).join(' ') + '
';
337 | html += '' + album.uri+ '
';
338 | if (copyrights.get('C')) {
339 | html += '© ' + copyrights.get('C').text; + '
';
340 | }
341 | if (copyrights.get('P')) {
342 | html += '℗ ' + copyrights.get('P').text + '
';
343 | }
344 | div.innerHTML = html;
345 |
346 | fragment.appendChild(div);
347 | });
348 | return fragment;
349 | }
350 |
351 | function fillInfoData(result, album) {
352 | var ids = result.ids;
353 | if (ids.length) {
354 | SpotifyApi.sendRequest(SPOTIFY_API + '/albums?ids=' + ids, function(error, albums) {
355 | console.log("Full found albums", albums.albums);
356 | var nest = d3.nest()
357 | .key(function(d) { return d.name; })
358 | .sortKeys(function(a, b) {
359 | return a == album.name ? 1 : -1;
360 | })
361 | .sortValues(function(a, b){
362 | return a.id == album.id ? -1 : 1;
363 | })
364 | .entries(albums.albums);
365 |
366 | nest.forEach(function(el) {
367 | infoContainer.insertBefore(getAlbumInfo(el.key, el.values, album.id), infoContainer.firstChild);
368 | });
369 |
370 | if(!infoContainer.querySelector(".searched")) {
371 | exactMatchNotFound(album);
372 | }
373 | });
374 | } else {
375 | exactMatchNotFound(album);
376 | }
377 | }
378 |
379 | function exactMatchNotFound(album) {
380 | var html = Mustache.render(albumNotFoundTemplate, {
381 | albumApiLink: album.href,
382 | albumUri: album.uri
383 | })
384 | infoContainer.innerHTML = html + infoContainer.innerHTML;
385 | }
386 |
387 | function fillChartData(result) {
388 | var marketsList = result.markets;
389 | var ids = result.ids;
390 | var data = [];
391 | var allMarkets = Object.keys(ALL_MARKETS);
392 |
393 | marketsList.forEach(function(markets, i) {
394 | markets.forEach(function(market) {
395 | var index = allMarkets.indexOf(market);
396 | if (index > -1) {
397 | allMarkets.splice(allMarkets.indexOf(market), 1);
398 | }
399 |
400 | var name = market;
401 | if (ALL_MARKETS[market]) {
402 | name = ALL_MARKETS[market];
403 | } else {
404 | console.log('Not in available markets', market);
405 | }
406 |
407 | var el = [name, result.count[market], result.count[market] + ' versions available'];
408 | el.spid = ids[i];
409 | data.push(el);
410 | });
411 | });
412 |
413 | if (allMarkets.length) {
414 | console.log("Not available in: " + allMarkets);
415 | var html = Mustache.render(albumNotAvailableTemplate, {
416 | markets: allMarkets.map(function(el) {
417 | return { code: el, name: ALL_MARKETS[el]}
418 | })
419 | });
420 | infoContainer.innerHTML += html;
421 | }
422 |
423 | allMarkets.forEach(function(market) {
424 | data.push([ALL_MARKETS[market], 0, 'Not available']);
425 | });
426 |
427 | console.log("Chart data", data);
428 | currentDataset = data;
429 | drawChart(data);
430 | }
431 |
432 | function drawChart(markets) {
433 | var chartData = new google.visualization.DataTable();
434 | chartData.addColumn('string', 'Country');
435 | chartData.addColumn('number', 'Available');
436 | chartData.addColumn({type: 'string', role: 'tooltip'});
437 | chartData.addRows(markets);
438 |
439 | var max = d3.max(markets, function(el) { return el[1] }) || 1;
440 |
441 | if (max < 1) max = 1;
442 |
443 | var options = {
444 | backgroundColor: 'transparent',
445 | datalessRegionColor: '#2e2f33',
446 | keepAspectRatio: true,
447 | colorAxis: {
448 | minValue: 0,
449 | colors: ['#8f2600', '#bdaa00', '#4B6601', '#B4D612'],
450 | values: [0, 0.5, 1, max]
451 | },
452 | legend: 'none',
453 | tooltip: {
454 | showColorCode: false
455 | },
456 | enableRegionInteractivity: true,
457 | animation: {
458 | duration: 1000,
459 | easing: 'out',
460 | }
461 | };
462 |
463 | chart.draw(chartData, options);
464 | }
465 |
466 | function authenticate() {
467 | var authUrl = Auth.getAuthUrl(SITE_URL);
468 | window.location = authUrl;
469 | }
470 |
471 | if (location.search.indexOf('auth_callback') > 0) {
472 | Auth.parseResponse(location);
473 | window.location = SITE_URL;
474 | return;
475 | }
476 |
477 | chart = new google.visualization.GeoChart(chartContainer);
478 |
479 | performSearch(Config.getLastSearch() || "18qY7zpuNqeXNGywRysjxx");
480 | }
481 |
--------------------------------------------------------------------------------