diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ab6fe6 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +[playlist manager](https://spotify.sarsoo.xyz) +================== + +playlist managing web app acting as a front-end for the [pyfmframework](https://github.com/Sarsoo/pyspotframework) playlist engine + diff --git a/spotify/api/api.py b/spotify/api/api.py index 6b87edb..6c42ac3 100644 --- a/spotify/api/api.py +++ b/spotify/api/api.py @@ -27,15 +27,15 @@ def get_playlists(): return jsonify({'error': 'not logged in'}), 401 -@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT']) -def get_playlist(): +@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT', 'DELETE']) +def playlist(): if 'username' in session: user_ref = database.get_user_doc_ref(session['username']) playlists = database.get_user_playlists_collection(user_ref.id) - if request.method == 'GET': + if request.method == 'GET' or request.method == 'DELETE': playlist_name = request.args.get('name', None) if playlist_name: @@ -47,7 +47,15 @@ def get_playlist(): elif len(queried_playlist) > 1: return jsonify({'error': 'multiple playlists found'}), 500 - return jsonify(queried_playlist[0].to_dict()), 200 + if request.method == "GET": + + return jsonify(queried_playlist[0].to_dict()), 200 + + elif request.method == 'DELETE': + + playlists.document(queried_playlist[0].id).delete() + + return jsonify({"message": 'playlist deleted', "status": "success"}), 200 else: return jsonify({"error": 'no name requested'}), 400 @@ -62,6 +70,7 @@ def get_playlist(): playlist_name = request_json['name'] playlist_parts = request_json.get('parts', None) playlist_id = request_json.get('id', None) + playlist_shuffle = request_json.get('shuffle', None) queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] @@ -70,18 +79,19 @@ def get_playlist(): if len(queried_playlist) != 0: return jsonify({'error': 'playlist already exists'}), 400 - if playlist_parts is None or playlist_id is None: - return jsonify({'error': 'parts and id required'}), 400 + # if playlist_id is None or playlist_shuffle is None: + # return jsonify({'error': 'parts and id required'}), 400 playlists.add({ 'name': playlist_name, 'parts': playlist_parts, - 'playlist_id': playlist_id + 'playlist_id': playlist_id, + 'shuffle': playlist_shuffle }) return jsonify({"message": 'playlist added', "status": "success"}), 200 - else: + elif request.method == 'POST': if len(queried_playlist) == 0: return jsonify({'error': "playlist doesn't exist"}), 400 @@ -89,7 +99,7 @@ def get_playlist(): if len(queried_playlist) > 1: return jsonify({'error': "multiple playlists exist"}), 500 - if playlist_parts is None and playlist_id is None: + if playlist_parts is None and playlist_id is None and playlist_shuffle is None: return jsonify({'error': "no chnages to make"}), 400 playlist_doc = playlists.document(queried_playlist[0].id) @@ -102,10 +112,15 @@ def get_playlist(): if playlist_id: dic['playlist_id'] = playlist_id + if playlist_shuffle is not None: + dic['shuffle'] = playlist_shuffle + playlist_doc.update(dic) return jsonify({"message": 'playlist updated', "status": "success"}), 200 + + else: return jsonify({'error': 'not logged in'}), 401 diff --git a/spotify/auth/auth.py b/spotify/auth/auth.py index 8ee98f4..752ceb5 100644 --- a/spotify/auth/auth.py +++ b/spotify/auth/auth.py @@ -78,7 +78,7 @@ def auth(): return redirect(urllib.parse.urlunparse(['https', 'accounts.spotify.com', 'authorize', '', params, ''])) - return redirect('/') + return redirect(url_for('index')) @blueprint.route('/spotify/token') @@ -116,7 +116,7 @@ def token(): return redirect('/app/settings/spotify') - return redirect('/') + return redirect(url_for('index')) @blueprint.route('/spotify/deauth') @@ -134,4 +134,4 @@ def deauth(): return redirect('/app/settings/spotify') - return redirect('/') + return redirect(url_for('index')) diff --git a/spotify/templates/app.html b/spotify/templates/app.html index 5cd6e30..496b033 100644 --- a/spotify/templates/app.html +++ b/spotify/templates/app.html @@ -23,9 +23,5 @@
- - \ No newline at end of file diff --git a/spotify/templates/index.html b/spotify/templates/index.html index 612e649..3136636 100644 --- a/spotify/templates/index.html +++ b/spotify/templates/index.html @@ -30,7 +30,7 @@

password

- + diff --git a/src/js/Index/Index.js b/src/js/Index/Index.js index 68ea663..52041fc 100644 --- a/src/js/Index/Index.js +++ b/src/js/Index/Index.js @@ -22,7 +22,7 @@ class Index extends Component{ } render(){ - return

welcome to playlist manager!

; + return

welcome to playlist manager!

; } } diff --git a/src/js/Playlist/NewPlaylist.js b/src/js/Playlist/NewPlaylist.js new file mode 100644 index 0000000..b71bf67 --- /dev/null +++ b/src/js/Playlist/NewPlaylist.js @@ -0,0 +1,93 @@ +import React, { Component } from "react"; +const axios = require('axios'); + +class NewPlaylist extends Component { + + constructor(props) { + super(props); + this.state = { + name: '', + type: 'normal', + error: false, + errorText: null + } + this.handleInputChange = this.handleInputChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleInputChange(event){ + this.setState({ + [event.target.name]: event.target.value + }); + } + + handleSubmit(event){ + axios.get('/api/playlists') + .then((response) => { + var sameName = response.data.playlists.filter((i) => {i.name == this.state.name ? true : false}); + if(sameName.length == 0){ + axios.put('/api/playlist', { + name: this.state.name, + parts: [], + shuffle: false, + type: this.state.type, + }).catch((error) => { + console.log(error); + }) + }else{ + this.setState({ + error: true, + errorText: 'name already exists' + }); + } + }); + console.log(this.state); + } + + render(){ + return ( + + + + + + + + + + + + + + + { this.state.error && + + + } + +
+

new playlist

+
+ + + +
+ +
+

{this.state.errorText}

+
+ ); + } + +} + +export default NewPlaylist; \ No newline at end of file diff --git a/src/js/Playlist/PlaylistView.js b/src/js/Playlist/PlaylistView.js index 4b9e4fa..6b97021 100644 --- a/src/js/Playlist/PlaylistView.js +++ b/src/js/Playlist/PlaylistView.js @@ -5,16 +5,19 @@ class PlaylistView extends Component{ constructor(props){ super(props); - console.log(this.props.match.params.name); this.state = { name: this.props.match.params.name, parts: [], error: false, error_text: null, - add_part_value: '' + newPlaylistName: '', + shuffle: false } this.handleAddPart = this.handleAddPart.bind(this); this.handleAddPartChange = this.handleAddPartChange.bind(this); + this.handleRemoveRow = this.handleRemoveRow.bind(this); + + this.handleShuffleChange = this.handleShuffleChange.bind(this); } componentDidMount(){ @@ -35,21 +38,46 @@ class PlaylistView extends Component{ handleAddPartChange(event){ this.setState({ - add_part_value: event.target.value + newPlaylistName: event.target.value + }); + } + + handleShuffleChange(event) { + this.setState({ + shuffle: event.target.checked + }); + axios.post('/api/playlist', { + name: this.state.name, + shuffle: event.target.checked + }).catch((error) => { + console.log(error); }); } handleAddPart(event){ var parts = this.state.parts; - parts.push(this.state.add_part_value); + parts.push(this.state.newPlaylistName); + this.setState({ + parts: parts, + add_part_value: '' + }); + axios.post('/api/playlist', { + name: this.state.name, + parts: parts + }).catch((error) => { + console.log(error); + }); + } + + handleRemoveRow(id, event){ + var parts = this.state.parts; + parts = parts.filter(e => e !== id); this.setState({ parts: parts }); axios.post('/api/playlist', { name: this.state.name, parts: parts - }).then((response) => { - console.log(reponse); }).catch((error) => { console.log(error); }); @@ -61,19 +89,33 @@ class PlaylistView extends Component{ - + - { this.state.parts.map((part) => ) } + { this.state.parts.map((part) => ) } + + + +

{ this.state.name }

{ this.state.name }

- +
+ shuffle output? + + +
); @@ -88,8 +130,8 @@ class PlaylistView extends Component{ function Row (props) { return ( - { props.part } - remove + { props.part } + ); } diff --git a/src/js/Playlist/Playlists.js b/src/js/Playlist/Playlists.js index 713b095..9312d27 100644 --- a/src/js/Playlist/Playlists.js +++ b/src/js/Playlist/Playlists.js @@ -1,19 +1,22 @@ import React, { Component } from "react"; -import { BrowserRouter as Router, Route, Link } from "react-router-dom"; +import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom"; const axios = require('axios'); import PlaylistsView from "./PlaylistsView.js" +import NewPlaylist from "./NewPlaylist.js"; class Playlists extends Component { render(){ return (
- - + + + +
); } diff --git a/src/js/Playlist/PlaylistsView.js b/src/js/Playlist/PlaylistsView.js index e5cc48e..ca3f0ea 100644 --- a/src/js/Playlist/PlaylistsView.js +++ b/src/js/Playlist/PlaylistsView.js @@ -10,6 +10,8 @@ class PlaylistsView extends Component { isLoading: true } this.getPlaylists(); + this.handleRunPlaylist = this.handleRunPlaylist.bind(this); + this.handleDeletePlaylist = this.handleDeletePlaylist.bind(this); } getPlaylists(){ @@ -23,9 +25,27 @@ class PlaylistsView extends Component { }); } + handleRunPlaylist(name, event){ + + } + + handleDeletePlaylist(name, event){ + axios.delete('/api/playlist', { params: { name: name } }) + .then((response) => { + this.getPlaylists(); + }).catch((error) => { + console.log(error); + }); + } + render() { - const table =
; + const table =
+
+ ; + const loadingMessage =

loading...

; return this.state.isLoading ? loadingMessage : table; @@ -36,7 +56,10 @@ function Table(props){ return (
- { props.playlists.map((playlist) => ) } + { props.playlists.map((playlist) => ) }
); @@ -46,6 +69,8 @@ function Row(props){ return ( + + ); } diff --git a/src/js/PlaylistManager.js b/src/js/PlaylistManager.js index 6e8a46d..c01f595 100644 --- a/src/js/PlaylistManager.js +++ b/src/js/PlaylistManager.js @@ -62,15 +62,17 @@ class PlaylistManager extends Component {
- - } /> + + { this.state.type == 'admin' && }
- + ); } diff --git a/src/js/Settings/ChangePassword.js b/src/js/Settings/ChangePassword.js index ee45edf..a9e4d18 100644 --- a/src/js/Settings/ChangePassword.js +++ b/src/js/Settings/ChangePassword.js @@ -84,12 +84,12 @@ class ChangePassword extends Component { - + - + - + - +

change password

change password

current:current:
new:new:
new again:new again: - } /> + ); diff --git a/src/js/Settings/SpotifyLink.js b/src/js/Settings/SpotifyLink.js index e246975..d099fa3 100644 --- a/src/js/Settings/SpotifyLink.js +++ b/src/js/Settings/SpotifyLink.js @@ -6,25 +6,33 @@ class SpotifyLink extends Component { constructor(props){ super(props); this.state = { - spotify_linked: props.spotify_linked + spotify_linked: null, + isLoading: true } + this.getUserInfo(); } getUserInfo(){ - + axios.get('/api/user') + .then((response) => { + this.setState({ + spotify_linked: response.data.spotify_linked, + isLoading: false + }) + }); } render(){ - return ( + const table = - + - @@ -34,8 +42,11 @@ class SpotifyLink extends Component { -

spotify link status

spotify link status

+ status: { this.state.spotify_linked ? "linked" : "unlinked" }
- ); +
; + + const loadingMessage =

loading...

; + + return this.state.isLoading ? loadingMessage : table; } } diff --git a/src/scss/style.scss b/src/scss/style.scss index dc0d734..54df3e7 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -70,6 +70,14 @@ p { } } +input[type=text], input[type=password], select { + padding: 10px; + background-color: #505050; + border: black; + border-radius: 4px; + color: white; +} + .row { margin: 30px; } @@ -273,7 +281,7 @@ ul.navbar { width: 100%; margin: auto; td { - padding: $pad-px; + padding: 5px; } } @@ -281,6 +289,14 @@ ul.navbar { max-width: 800px; } +.text-no-select { + cursor: default; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + footer { p { text-align: right;