From 994cc871772f26088cc0693898f8c88893bb0df7 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Wed, 20 Apr 2016 19:29:16 +0200 Subject: [PATCH 1/8] Start on React with static data and no auth --- public/index.html | 27 ++-- public/script.js | 305 +++++++++++++++++++++++----------------------- 2 files changed, 163 insertions(+), 169 deletions(-) diff --git a/public/index.html b/public/index.html index 2fcd3b1..1911af9 100644 --- a/public/index.html +++ b/public/index.html @@ -27,32 +27,21 @@ - +
-
- -
- - - - +
+ + + + + - + diff --git a/public/script.js b/public/script.js index c139d1a..f1e66e2 100644 --- a/public/script.js +++ b/public/script.js @@ -1,156 +1,161 @@ 'use strict'; -(function(){ +var data = [ + {id: 1, name: "Mine"}, + {id: 2, name: "Other"}, + {id: 3, name: "A third"} +]; - var app = angular.module('app',['ui.router', 'ngStorage']); - - app.config(['$stateProvider','$urlRouterProvider',function($stateProvider, $urlRouterProvider){ - $stateProvider - .state('finder_public',{ - templateUrl:'partials/home.html', - url: '/', - controller: 'HomeCtrl' - }) - .state('finder',{ - abstract: true, - templateUrl:'partials/logged.html', - url: '/finder', - controller: 'MainCtrl' - }) - .state('finder.playlist',{ - url: '/playlist', - views: { - playlist:{ - templateUrl: 'partials/playlist.html', - controller: 'PlaylistCtrl' - }, - dups:{ - template: '' - } +var dups = [ + { + track: { + album: { + id: "53fWaWYPGghRHppKdD7A2S", + name: "El Taxi (feat. Pitbull, Sensato)" + }, + artists: [ + { + id: "6W0XSFVBD0xJlJhahPSlKZ", + name: "Osmani Garcia" + }, { + id: "0TnOYISbd1XYRBk9myaseg", + name: "Pitbull" + }, { + id: "7iJrDbKM5fEkGdm5kpjFzS" } - }) - .state('finder.playlist.dups',{ - url: '/:uid/:id/:playlist', - views: { - 'dups@finder': { - templateUrl: 'partials/dups.html', - controller: 'DupsCtrl' - } - } - }); - $urlRouterProvider.otherwise("/"); - }]); - - app.run(['$rootScope', '$state', '$localStorage',function($rootScope, $state, $localStorage){ - $rootScope.$storage = $localStorage.$default({ - access_token: "", - refresh_token: "" - }); - $rootScope.access_token = $rootScope.$storage.access_token; - $rootScope.refresh_token = $rootScope.$storage.refresh_token; - - $rootScope.$watch('access_token', function(newVal, oldVal, scope){ - - $rootScope.$storage.access_token = $rootScope.access_token; - $rootScope.$storage.refresh_token = $rootScope.refresh_token; - - if(oldVal == "" && newVal != ""){ - $state.go('finder.playlist'); - } else if (oldVal != "" && newVal == ""){ - $state.go('finder_public'); - } - }); - $rootScope.$on('$stateChangeStart', - function (event, toState) { - if ((toState.name != "finder_public") && $rootScope.access_token == "") { - event.preventDefault(); - $state.go('finder_public'); - } - }); - }]); - - app.controller("HomeCtrl",['$scope',function($scope){ - - }]); - - app.controller("MainCtrl",['$scope', '$state',function($scope, $state){ - $scope.current = {playlistId: undefined}; - }]); - - app.controller("PlaylistCtrl",['$scope', '$http', '$state',function($scope, $http, $state){ - $scope.playlists = []; - $scope.load = function(){ - $http.get('/get_playlists', { - params:{ - access_token: $scope.access_token - } - }).then(function(result){ - var r = result.data; - if(r.data){ - $scope.playlists = r.data; - } - }) - }; - $scope.open = function(playlist) { - $scope.current.playlistId = playlist.id; - $state.go('finder.playlist.dups',{uid: playlist.owner.id, id: playlist.id, playlist: encodeURIComponent(playlist.name)}) - }; - $scope.load(); - }]); - - app.controller("DupsCtrl",['$scope', '$stateParams', '$http',function($scope, $stateParams, $http){ - $scope.uid = $stateParams['uid']; - $scope.id = $stateParams['id']; - $scope.playlistName = decodeURIComponent($stateParams.playlist); - $scope.current.playlistId = $scope.id; - $scope.tracks = []; - $scope.loaded = false; - $scope.load = function () { - $http.get('/pl/' + $scope.uid + '/' + $scope.id, { - params: { - access_token: $scope.access_token - } - }).then(function(result) { - var r = result.data; - $scope.tracks = r.data; - $scope.loaded = true; - }); - }; - $scope.load(); - }]); - - app.controller('AuthCtrl',['$scope', '$rootScope', '$interval', '$http', function($scope, $rootScope, $interval, $http){ - $scope.login = function(){ - var openUrl = '/login'; - window.$windowScope = $scope; - $scope.popup = window.open(openUrl, "Authenticate Account", "width=500, height=500"); - var checker = $interval(function(){ - if($scope.popup.closed){ - $interval.cancel(checker); - } else if ($scope.popup.token != undefined && $scope.popup.token != null) { - $rootScope.access_token = $scope.popup.token.access_token; - $rootScope.refresh_token = $scope.popup.token.refresh_token; - $scope.popup.close(); - $interval.cancel(checker); - } - }, 500); - return false; - }; - $scope.logout = function(){ - $rootScope.refresh_token = ""; - $rootScope.access_token = ""; - }; - $scope.refresh = function(){ - if($rootScope.refresh_token) - $http.get('/refresh_token',{params: {refresh_token: $rootScope.refresh_token}}) - .then(function(result){ - $rootScope.access_token = result.data.access_token || ""; - }, function(failResult){ - $rootScope.access_token = ""; - $rootScope.refresh_token = ""; - }); - return false; + ], + href: "https://api.spotify.com/v1/tracks/1qpbJ8GiPc706AfGqZAIei", + id: "1qpbJ8GiPc706AfGqZAIei", + name: "El Taxi" } - }]) + }, { + track: { + album: { + id: "53fWaWYPGghRHppKdD7A2S", + name: "El Taxi (feat. Pitbull, Sensato)" + }, + artists: [ + { + id: "6W0XSFVBD0xJlJhahPSlKZ", + name: "Osmani Garcia" + }, { + id: "0TnOYISbd1XYRBk9myaseg", + name: "Pitbull" + }, { + id: "7iJrDbKM5fEkGdm5kpjFzS" + } + ], + href: "https://api.spotify.com/v1/tracks/1qpbJ8GiPc706AfGqZAIei", + id: "zkefjzkefn", + name: "El Taxi" + } + } +]; -})(); \ No newline at end of file +var Duplicate = React.createClass({ + render: function () { + var authors = ""; + this.props.data.artists.forEach(function (item, index) { + if (index != 0) { + authors += ', '; + } + authors += item.name; + }); + return ( +
{this.props.data.name} - {authors}
+ ); + } +}); + +var DuplicatesBox = React.createClass({ + render: function () { + var duplicates = this.props.data.map(function (duplicate) { + return ( + + ); + }); + return ( +
+ {duplicates} +
+ ); + } +}); + + +var Playlist = React.createClass({ + render: function () { + return ( + {this.props.name} + ); + } +}); + +var PlaylistBox = React.createClass({ + render: function () { + var playlists = this.props.data.map(function (playlist) { + return ( + + ); + }); + return ( +
+ {playlists} +
+ ); + } +}); + +var DuplicateFinderBox = React.createClass({ + render: function () { + return ( +
+
+

Playlists

+ +
+
+

Duplicates

+ +
+
+ ) + } +}); + +ReactDOM.render( + , + document.getElementById('content') +); + +var Authenticate = React.createClass({ + render: function () { + var accessToken = ""; + if (accessToken == "") { + return ( + + ); + } + else { + return ( + + ); + } + } +}); + +ReactDOM.render( + , + document.getElementById('authentication') +); From b50d2236c3a2951b7160c8c18f63f9f068a4fb30 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Thu, 21 Apr 2016 01:10:27 +0200 Subject: [PATCH 2/8] Work with React ! --- app.js | 11 +- public/index.html | 24 +--- public/script.js | 273 +++++++++++++++++++++++++++++++++++++++------- 3 files changed, 243 insertions(+), 65 deletions(-) diff --git a/app.js b/app.js index 434b9c0..4cb22b7 100644 --- a/app.js +++ b/app.js @@ -91,9 +91,16 @@ app.get('/callback', function (req, res) { refresh_token = body.refresh_token; // we can also pass the token to the browser to make requests from there - res.send('') + res.redirect('/#' + + querystring.stringify({ + access_token: access_token, + refresh_token: refresh_token + })); } else { - res.send('') + res.redirect('/#' + + querystring.stringify({ + error: 'invalid_token' + })); } }); } diff --git a/public/index.html b/public/index.html index 1911af9..905efbe 100644 --- a/public/index.html +++ b/public/index.html @@ -9,30 +9,8 @@ - - -
+
diff --git a/public/script.js b/public/script.js index f1e66e2..5c03bea 100644 --- a/public/script.js +++ b/public/script.js @@ -1,9 +1,36 @@ 'use strict'; +/** + * Obtains parameters from the hash of the URL + * @return Object + */ +function getHashParams() { + var hashParams = {}; + var e, r = /([^&;=]+)=?([^&;]*)/g, + q = window.location.hash.substring(1); + while (e = r.exec(q)) { + hashParams[e[1]] = decodeURIComponent(e[2]); + } + return hashParams; +} + + var data = [ - {id: 1, name: "Mine"}, - {id: 2, name: "Other"}, - {id: 3, name: "A third"} + { + href: "https://api.spotify.com/v1/users/wizzler/playlists/53Y8wT46QIMz5H4WQ8O22c", + id: "53Y8wT46QIMz5H4WQ8O22c", + name: "Wizzlers Big Playlist", + owner: { + id: "wizzler" + } + }, { + href: "https://api.spotify.com/v1/users/wizzlersmate/playlists/1AVZz0mBuGbCEoNRQdYQju", + id: "1AVZz0mBuGbCEoNRQdYQju", + name: "Another Playlist", + owner: { + id: "wizzlersmate" + } + } ]; var dups = [ @@ -69,35 +96,49 @@ var Duplicate = React.createClass({ var DuplicatesBox = React.createClass({ render: function () { - var duplicates = this.props.data.map(function (duplicate) { + if (this.props.data && this.props.data.length > 0) { + var duplicates = this.props.data.map(function (duplicate) { + return ( + + ); + }); return ( - +
+ {duplicates} +
); - }); - return ( -
- {duplicates} -
- ); + } + else { + return ( +

No duplicate found

+ ); + } } }); -var Playlist = React.createClass({ - render: function () { - return ( - {this.props.name} - ); - } -}); - var PlaylistBox = React.createClass({ + getInitialState: function () { + return { + currentId: null + }; + }, + clickOnItem: function (id, uid) { + this.setState({currentId: id}); + this.props.handleClick(id, uid); + }, render: function () { - var playlists = this.props.data.map(function (playlist) { - return ( - - ); - }); + var currentId = this.state.currentId; + var playlists; + if (this.props.data) { + playlists = this.props.data.map(function (pl) { + var id = pl.id; + var classes = "list-group-item" + (currentId == id ? " active" : ""); + return ( + {pl.name} + ); + }, this); + } return (
{playlists} @@ -107,55 +148,207 @@ var PlaylistBox = React.createClass({ }); var DuplicateFinderBox = React.createClass({ + getInitialState: function () { + return { + currentId: null, + currentUId: null, + dups: null, + loading: false + }; + }, + handlePlaylistClick: function (id, uid) { + this.setState({ + currentId: id, + currentUId: uid, + loading: true + }); + var self = this; + + $.ajax({ + url: "/pl/" + uid + "/" + id, + data: { + 'access_token': self.props.auth.access_token + }, + success: function (data) { + var dups = data.data; + self.setState({ + dups: dups, + loading: false + }); + }, + error: function (xhr, response, err) { + console.error(response, err); + } + }); + }, render: function () { + var duplicates =

Loading...

; + if (!this.state.loading) { + duplicates = ; + } return (

Playlists

- +

Duplicates

- + {duplicates}
) } }); -ReactDOM.render( - , - document.getElementById('content') -); - var Authenticate = React.createClass({ + logout: function () { + this.props.refreshAuth(null, null); + }, + refreshToken: function (event) { + event.preventDefault(); + var self = this; + $.ajax({ + url: '/refresh_token', + data: { + 'refresh_token': self.props.auth.refresh_token + }, + success: function (data) { + self.props.refreshAuth(data.access_token, self.props.auth.refresh_token); + }, + error: function (xhr, status, err) { + console.error(status, err); + } + }); + }, render: function () { - var accessToken = ""; - if (accessToken == "") { - return ( + var auth; + if (this.props.auth.access_token == null) { + auth = ( ); } else { - return ( + auth = ( ); } + + return ( + + ); + } +}); + +var App = React.createClass({ + getInitialState: function () { + var params = getHashParams(); + var access_token = params.access_token || null; + var refresh_token = params.refresh_token || null; + + return { + access_token: access_token, + refresh_token: refresh_token, + playlists: null + }; + }, + getPlaylists: function () { + var self = this; + $.ajax({ + url: '/get_playlists', + data: { + 'access_token': this.state.access_token + }, + success: function (data) { + var pl = data.data; + self.setState({ + playlists: pl + }); + }, + error: function (xhr, response, err) { + console.error(response, err); + } + }); + }, + isLoggedIn: function () { + return !(this.state.access_token == null && this.state.refresh_token == null); + }, + refreshAuth: function (access, refresh) { + if (access == null && refresh == null) { + this.setState({ + access_token: access, + refresh_token: refresh, + playlists: null + }); + } + else { + this.setState({ + access_token: access, + refresh_token: refresh + }); + this.getPlaylists(); + } + }, + componentDidMount: function () { + if (this.state.access_token) { + this.getPlaylists(); + } + }, + render: function () { + var auth = { + access_token: this.state.access_token, + refresh_token: this.state.refresh_token + }; + var content =

Please log in with Spotify :)

+ if (this.isLoggedIn()) { + content = ( + + ); + } + + return ( +
+ +
+ {content} +
+
+ ); } }); ReactDOM.render( - , - document.getElementById('authentication') + , + document.getElementById('content') ); From 6ed13cc1bec1505947f3c0696a548c49de02f29d Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Thu, 21 Apr 2016 01:11:10 +0200 Subject: [PATCH 3/8] Remove default data --- public/script.js | 65 ------------------------------------------------ 1 file changed, 65 deletions(-) diff --git a/public/script.js b/public/script.js index 5c03bea..1988775 100644 --- a/public/script.js +++ b/public/script.js @@ -14,71 +14,6 @@ function getHashParams() { return hashParams; } - -var data = [ - { - href: "https://api.spotify.com/v1/users/wizzler/playlists/53Y8wT46QIMz5H4WQ8O22c", - id: "53Y8wT46QIMz5H4WQ8O22c", - name: "Wizzlers Big Playlist", - owner: { - id: "wizzler" - } - }, { - href: "https://api.spotify.com/v1/users/wizzlersmate/playlists/1AVZz0mBuGbCEoNRQdYQju", - id: "1AVZz0mBuGbCEoNRQdYQju", - name: "Another Playlist", - owner: { - id: "wizzlersmate" - } - } -]; - -var dups = [ - { - track: { - album: { - id: "53fWaWYPGghRHppKdD7A2S", - name: "El Taxi (feat. Pitbull, Sensato)" - }, - artists: [ - { - id: "6W0XSFVBD0xJlJhahPSlKZ", - name: "Osmani Garcia" - }, { - id: "0TnOYISbd1XYRBk9myaseg", - name: "Pitbull" - }, { - id: "7iJrDbKM5fEkGdm5kpjFzS" - } - ], - href: "https://api.spotify.com/v1/tracks/1qpbJ8GiPc706AfGqZAIei", - id: "1qpbJ8GiPc706AfGqZAIei", - name: "El Taxi" - } - }, { - track: { - album: { - id: "53fWaWYPGghRHppKdD7A2S", - name: "El Taxi (feat. Pitbull, Sensato)" - }, - artists: [ - { - id: "6W0XSFVBD0xJlJhahPSlKZ", - name: "Osmani Garcia" - }, { - id: "0TnOYISbd1XYRBk9myaseg", - name: "Pitbull" - }, { - id: "7iJrDbKM5fEkGdm5kpjFzS" - } - ], - href: "https://api.spotify.com/v1/tracks/1qpbJ8GiPc706AfGqZAIei", - id: "zkefjzkefn", - name: "El Taxi" - } - } -]; - var Duplicate = React.createClass({ render: function () { var authors = ""; From b4a0b88687fba02484c2dc46c710f2e7274f3c62 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Thu, 21 Apr 2016 01:12:41 +0200 Subject: [PATCH 4/8] Use bootstrap fluid --- public/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index 1988775..4cb6ae0 100644 --- a/public/script.js +++ b/public/script.js @@ -275,7 +275,7 @@ var App = React.createClass({ return (
-
+
{content}
From e09833fab85c1f36adcf9e035616719e6611c004 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Thu, 21 Apr 2016 01:17:57 +0200 Subject: [PATCH 5/8] Improve refresh auth --- public/script.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/public/script.js b/public/script.js index 4cb6ae0..58392d3 100644 --- a/public/script.js +++ b/public/script.js @@ -240,23 +240,17 @@ var App = React.createClass({ return !(this.state.access_token == null && this.state.refresh_token == null); }, refreshAuth: function (access, refresh) { - if (access == null && refresh == null) { - this.setState({ - access_token: access, - refresh_token: refresh, - playlists: null - }); - } - else { - this.setState({ - access_token: access, - refresh_token: refresh - }); + this.setState({ + access_token: access, + refresh_token: refresh + }); + + if (!(access == null && refresh == null)) { this.getPlaylists(); } }, componentDidMount: function () { - if (this.state.access_token) { + if (this.isLoggedIn()) { this.getPlaylists(); } }, @@ -265,10 +259,10 @@ var App = React.createClass({ access_token: this.state.access_token, refresh_token: this.state.refresh_token }; - var content =

Please log in with Spotify :)

+ var content =

Please log in with Spotify :)

; if (this.isLoggedIn()) { content = ( - + ); } From 006c34c68b41ed2376fee61016e06b0d3af94261 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Thu, 21 Apr 2016 01:20:29 +0200 Subject: [PATCH 6/8] Remove partials --- public/partials/dups.html | 8 -------- public/partials/home.html | 1 - public/partials/logged.html | 4 ---- public/partials/playlist.html | 8 -------- 4 files changed, 21 deletions(-) delete mode 100644 public/partials/dups.html delete mode 100644 public/partials/home.html delete mode 100644 public/partials/logged.html delete mode 100644 public/partials/playlist.html diff --git a/public/partials/dups.html b/public/partials/dups.html deleted file mode 100644 index b8b63d7..0000000 --- a/public/partials/dups.html +++ /dev/null @@ -1,8 +0,0 @@ -

Duplicates in {{playlistName}}

-
Loading...
-
No duplicate found
-
-
- {{t.track.name}} - {{artist == t.track.artists[0]?'':', '}}{{artist.name}} -
-
\ No newline at end of file diff --git a/public/partials/home.html b/public/partials/home.html deleted file mode 100644 index c80bb30..0000000 --- a/public/partials/home.html +++ /dev/null @@ -1 +0,0 @@ -

Please connect to Spotify to access to the app.

\ No newline at end of file diff --git a/public/partials/logged.html b/public/partials/logged.html deleted file mode 100644 index 050450b..0000000 --- a/public/partials/logged.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
\ No newline at end of file diff --git a/public/partials/playlist.html b/public/partials/playlist.html deleted file mode 100644 index 09b8638..0000000 --- a/public/partials/playlist.html +++ /dev/null @@ -1,8 +0,0 @@ -
-

Playlist

-
- \ No newline at end of file From 2093e1c86bac6902dca791affc32c34f157ee022 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Thu, 21 Apr 2016 01:26:21 +0200 Subject: [PATCH 7/8] Refactor code --- public/script.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/public/script.js b/public/script.js index 58392d3..75d84e8 100644 --- a/public/script.js +++ b/public/script.js @@ -17,24 +17,24 @@ function getHashParams() { var Duplicate = React.createClass({ render: function () { var authors = ""; - this.props.data.artists.forEach(function (item, index) { + this.props.track.artists.forEach(function (item, index) { if (index != 0) { authors += ', '; } authors += item.name; }); return ( -
{this.props.data.name} - {authors}
+
{this.props.track.name} - {authors}
); } }); var DuplicatesBox = React.createClass({ render: function () { - if (this.props.data && this.props.data.length > 0) { - var duplicates = this.props.data.map(function (duplicate) { + if (this.props.dups && this.props.dups.length > 0) { + var duplicates = this.props.dups.map(function (duplicate) { return ( - + ); }); return ( @@ -65,8 +65,8 @@ var PlaylistBox = React.createClass({ render: function () { var currentId = this.state.currentId; var playlists; - if (this.props.data) { - playlists = this.props.data.map(function (pl) { + if (this.props.playlists) { + playlists = this.props.playlists.map(function (pl) { var id = pl.id; var classes = "list-group-item" + (currentId == id ? " active" : ""); return ( @@ -119,13 +119,13 @@ var DuplicateFinderBox = React.createClass({ render: function () { var duplicates =

Loading...

; if (!this.state.loading) { - duplicates = ; + duplicates = ; } return (

Playlists

- +

Duplicates

From 1990f0e58a86ba5d29e2dce216a7c552b9afaf15 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Thu, 21 Apr 2016 02:02:16 +0200 Subject: [PATCH 8/8] Show when loading playlists --- public/script.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/public/script.js b/public/script.js index 75d84e8..0ec532e 100644 --- a/public/script.js +++ b/public/script.js @@ -88,14 +88,14 @@ var DuplicateFinderBox = React.createClass({ currentId: null, currentUId: null, dups: null, - loading: false + dupsLoading: false }; }, handlePlaylistClick: function (id, uid) { this.setState({ currentId: id, currentUId: uid, - loading: true + dupsLoading: true }); var self = this; @@ -108,7 +108,7 @@ var DuplicateFinderBox = React.createClass({ var dups = data.data; self.setState({ dups: dups, - loading: false + dupsLoading: false }); }, error: function (xhr, response, err) { @@ -118,21 +118,25 @@ var DuplicateFinderBox = React.createClass({ }, render: function () { var duplicates =

Loading...

; - if (!this.state.loading) { + if (!this.state.dupsLoading) { duplicates = ; } + var playlistBox =

Loading...

; + if (!this.props.playlistsLoading) { + playlistBox = ; + } return (

Playlists

- + {playlistBox}

Duplicates

{duplicates}
- ) + ); } }); @@ -215,10 +219,14 @@ var App = React.createClass({ return { access_token: access_token, refresh_token: refresh_token, - playlists: null + playlists: null, + playlistsLoading: true }; }, getPlaylists: function () { + this.setState({ + playlistsLoading: true + }); var self = this; $.ajax({ url: '/get_playlists', @@ -228,7 +236,8 @@ var App = React.createClass({ success: function (data) { var pl = data.data; self.setState({ - playlists: pl + playlists: pl, + playlistsLoading: false }); }, error: function (xhr, response, err) { @@ -261,8 +270,9 @@ var App = React.createClass({ }; var content =

Please log in with Spotify :)

; if (this.isLoggedIn()) { + var playlistsLoading = this.state.playlistsLoading; content = ( - + ); }