added play scratch pad
This commit is contained in:
parent
57408efcda
commit
dcfd197d93
2
app.yaml
2
app.yaml
@ -9,3 +9,5 @@ handlers:
|
|||||||
script: auto
|
script: auto
|
||||||
secure: always
|
secure: always
|
||||||
|
|
||||||
|
env_variables:
|
||||||
|
DEPLOY_DESTINATION: 'PROD'
|
@ -1,5 +1,6 @@
|
|||||||
from flask import Blueprint, session, request, jsonify
|
from flask import Blueprint, session, request, jsonify
|
||||||
|
|
||||||
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ from google.protobuf import timestamp_pb2
|
|||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
from spotify.tasks.run_user_playlist import run_user_playlist as run_user_playlist
|
from spotify.tasks.run_user_playlist import run_user_playlist as run_user_playlist
|
||||||
|
from spotify.tasks.play_user_playlist import play_user_playlist as play_user_playlist
|
||||||
|
|
||||||
import spotify.api.database as database
|
import spotify.api.database as database
|
||||||
|
|
||||||
@ -169,6 +171,9 @@ def playlist():
|
|||||||
if playlist_recommendation_sample is not None:
|
if playlist_recommendation_sample is not None:
|
||||||
dic['recommendation_sample'] = playlist_recommendation_sample
|
dic['recommendation_sample'] = playlist_recommendation_sample
|
||||||
|
|
||||||
|
if playlist_type is not None:
|
||||||
|
dic['type'] = playlist_type
|
||||||
|
|
||||||
if len(dic) == 0:
|
if len(dic) == 0:
|
||||||
return jsonify({"message": 'no changes to make', "status": "error"}), 400
|
return jsonify({"message": 'no changes to make', "status": "error"}), 400
|
||||||
|
|
||||||
@ -300,6 +305,76 @@ def change_password():
|
|||||||
return jsonify({'error': 'not logged in'}), 401
|
return jsonify({'error': 'not logged in'}), 401
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/playlist/play', methods=['POST'])
|
||||||
|
def play_playlist():
|
||||||
|
|
||||||
|
if 'username' in session:
|
||||||
|
|
||||||
|
request_json = request.get_json()
|
||||||
|
|
||||||
|
request_parts = request_json.get('parts', None)
|
||||||
|
request_playlist_type = request_json.get('playlist_type', 'default')
|
||||||
|
request_playlists = request_json.get('playlists', None)
|
||||||
|
request_shuffle = request_json.get('shuffle', False)
|
||||||
|
request_include_recommendations = request_json.get('include_recommendations', True)
|
||||||
|
request_recommendation_sample = request_json.get('recommendation_sample', 10)
|
||||||
|
request_day_boundary = request_json.get('day_boundary', 10)
|
||||||
|
|
||||||
|
if request_parts or request_playlists:
|
||||||
|
if len(request_parts) > 0 or len(request_playlists) > 0:
|
||||||
|
|
||||||
|
if os.environ.get('DEPLOY_DESTINATION', None) and os.environ['DEPLOY_DESTINATION'] == 'PROD':
|
||||||
|
create_play_user_playlist_task(session['username'],
|
||||||
|
parts=request_parts,
|
||||||
|
playlist_type=request_playlist_type,
|
||||||
|
playlists=request_playlists,
|
||||||
|
shuffle=request_shuffle,
|
||||||
|
include_recommendations=request_include_recommendations,
|
||||||
|
recommendation_sample=request_recommendation_sample,
|
||||||
|
day_boundary=request_day_boundary)
|
||||||
|
else:
|
||||||
|
play_user_playlist(session['username'],
|
||||||
|
parts=request_parts,
|
||||||
|
playlist_type=request_playlist_type,
|
||||||
|
playlists=request_playlists,
|
||||||
|
shuffle=request_shuffle,
|
||||||
|
include_recommendations=request_include_recommendations,
|
||||||
|
recommendation_sample=request_recommendation_sample,
|
||||||
|
day_boundary=request_day_boundary)
|
||||||
|
|
||||||
|
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
|
||||||
|
|
||||||
|
else:
|
||||||
|
return jsonify({'error': 'insufficient playlist sources'}), 400
|
||||||
|
|
||||||
|
else:
|
||||||
|
return jsonify({'error': 'insufficient playlist sources'}), 400
|
||||||
|
|
||||||
|
else:
|
||||||
|
return jsonify({'error': 'not logged in'}), 401
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/playlist/play/task', methods=['POST'])
|
||||||
|
def play_playlist_task():
|
||||||
|
if request.headers.get('X-AppEngine-QueueName', None):
|
||||||
|
payload = request.get_data(as_text=True)
|
||||||
|
if payload:
|
||||||
|
payload = json.loads(payload)
|
||||||
|
|
||||||
|
play_user_playlist(payload['username'],
|
||||||
|
parts=payload['parts'],
|
||||||
|
playlist_type=payload['playlist_type'],
|
||||||
|
playlists=payload['playlists'],
|
||||||
|
shuffle=payload['shuffle'],
|
||||||
|
include_recommendations=payload['include_recommendations'],
|
||||||
|
recommendation_sample=payload['recommendation_sample'],
|
||||||
|
day_boundary=payload['day_boundary'])
|
||||||
|
|
||||||
|
return jsonify({'message': 'executed playlist', 'status': 'success'}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({'error': 'unauthorized'}), 401
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/playlist/run', methods=['GET'])
|
@blueprint.route('/playlist/run', methods=['GET'])
|
||||||
def run_playlist():
|
def run_playlist():
|
||||||
|
|
||||||
@ -309,7 +384,10 @@ def run_playlist():
|
|||||||
|
|
||||||
if playlist_name:
|
if playlist_name:
|
||||||
|
|
||||||
run_user_playlist(session['username'], playlist_name)
|
if os.environ.get('DEPLOY_DESTINATION', None) and os.environ['DEPLOY_DESTINATION'] == 'PROD':
|
||||||
|
create_run_user_playlist_task(session['username'], playlist_name)
|
||||||
|
else:
|
||||||
|
run_user_playlist(session['username'], playlist_name)
|
||||||
|
|
||||||
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
|
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
|
||||||
|
|
||||||
@ -432,31 +510,78 @@ def execute_user(username):
|
|||||||
if len(iterate_playlist['parts']) > 0 or len(iterate_playlist['playlist_references']) > 0:
|
if len(iterate_playlist['parts']) > 0 or len(iterate_playlist['playlist_references']) > 0:
|
||||||
if iterate_playlist.get('playlist_id', None):
|
if iterate_playlist.get('playlist_id', None):
|
||||||
|
|
||||||
task = {
|
if os.environ.get('DEPLOY_DESTINATION', None) and os.environ['DEPLOY_DESTINATION'] == 'PROD':
|
||||||
'app_engine_http_request': { # Specify the type of request.
|
create_run_user_playlist_task(username, iterate_playlist['name'], seconds_delay)
|
||||||
'http_method': 'POST',
|
else:
|
||||||
'relative_uri': '/api/playlist/run/task',
|
run_playlist(username, iterate_playlist['name'])
|
||||||
'body': json.dumps({
|
|
||||||
'username': username,
|
|
||||||
'name': iterate_playlist['name']
|
|
||||||
}).encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds_delay)
|
seconds_delay += 6
|
||||||
|
|
||||||
# Create Timestamp protobuf.
|
|
||||||
timestamp = timestamp_pb2.Timestamp()
|
|
||||||
timestamp.FromDatetime(d)
|
|
||||||
|
|
||||||
# Add the timestamp to the tasks.
|
def create_run_user_playlist_task(username, playlist_name, delay=0):
|
||||||
task['schedule_time'] = timestamp
|
|
||||||
|
|
||||||
tasker.create_task(task_path, task)
|
task = {
|
||||||
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
|
'http_method': 'POST',
|
||||||
|
'relative_uri': '/api/playlist/run/task',
|
||||||
|
'body': json.dumps({
|
||||||
|
'username': username,
|
||||||
|
'name': playlist_name
|
||||||
|
}).encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
seconds_delay += 10
|
if delay > 0:
|
||||||
|
|
||||||
# execute_playlist(username, iterate_playlist['name'])
|
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
||||||
|
|
||||||
|
# Create Timestamp protobuf.
|
||||||
|
timestamp = timestamp_pb2.Timestamp()
|
||||||
|
timestamp.FromDatetime(d)
|
||||||
|
|
||||||
|
# Add the timestamp to the tasks.
|
||||||
|
task['schedule_time'] = timestamp
|
||||||
|
|
||||||
|
tasker.create_task(task_path, task)
|
||||||
|
|
||||||
|
|
||||||
|
def create_play_user_playlist_task(username,
|
||||||
|
parts=None,
|
||||||
|
playlist_type='default',
|
||||||
|
playlists=None,
|
||||||
|
shuffle=False,
|
||||||
|
include_recommendations=False,
|
||||||
|
recommendation_sample=10,
|
||||||
|
day_boundary=10,
|
||||||
|
delay=0):
|
||||||
|
task = {
|
||||||
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
|
'http_method': 'POST',
|
||||||
|
'relative_uri': '/api/playlist/play/task',
|
||||||
|
'body': json.dumps({
|
||||||
|
'username': username,
|
||||||
|
'playlist_type': playlist_type,
|
||||||
|
'parts': parts,
|
||||||
|
'playlists': playlists,
|
||||||
|
'shuffle': shuffle,
|
||||||
|
'include_recommendations': include_recommendations,
|
||||||
|
'recommendation_sample': recommendation_sample,
|
||||||
|
'day_boundary': day_boundary
|
||||||
|
}).encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if delay > 0:
|
||||||
|
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
||||||
|
|
||||||
|
# Create Timestamp protobuf.
|
||||||
|
timestamp = timestamp_pb2.Timestamp()
|
||||||
|
timestamp.FromDatetime(d)
|
||||||
|
|
||||||
|
# Add the timestamp to the tasks.
|
||||||
|
task['schedule_time'] = timestamp
|
||||||
|
|
||||||
|
tasker.create_task(task_path, task)
|
||||||
|
|
||||||
|
|
||||||
def push_run_user_playlist_message(username, name):
|
def push_run_user_playlist_message(username, name):
|
||||||
|
@ -127,7 +127,7 @@ def auth():
|
|||||||
{
|
{
|
||||||
'client_id': client_id,
|
'client_id': client_id,
|
||||||
'response_type': 'code',
|
'response_type': 'code',
|
||||||
'scope': 'playlist-modify-public playlist-modify-private playlist-read-private',
|
'scope': 'playlist-modify-public playlist-modify-private playlist-read-private user-modify-playback-state',
|
||||||
'redirect_uri': 'https://spotify.sarsoo.xyz/auth/spotify/token'
|
'redirect_uri': 'https://spotify.sarsoo.xyz/auth/spotify/token'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -19,12 +19,19 @@ logger.setLevel('INFO')
|
|||||||
log_format = '%(levelname)s %(name)s:%(funcName)s - %(message)s'
|
log_format = '%(levelname)s %(name)s:%(funcName)s - %(message)s'
|
||||||
formatter = logging.Formatter(log_format)
|
formatter = logging.Formatter(log_format)
|
||||||
|
|
||||||
client = glogging.Client()
|
if os.environ.get('DEPLOY_DESTINATION', None) and os.environ['DEPLOY_DESTINATION'] == 'PROD':
|
||||||
handler = CloudLoggingHandler(client)
|
client = glogging.Client()
|
||||||
|
handler = CloudLoggingHandler(client)
|
||||||
|
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
else:
|
||||||
|
stream_handler = logging.StreamHandler()
|
||||||
|
stream_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
|
|
||||||
app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'build'), template_folder="templates")
|
app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'build'), template_folder="templates")
|
||||||
app.secret_key = db.collection(u'spotify').document(u'config').get().to_dict()['secret_key']
|
app.secret_key = db.collection(u'spotify').document(u'config').get().to_dict()['secret_key']
|
||||||
|
111
spotify/tasks/play_user_playlist.py
Normal file
111
spotify/tasks/play_user_playlist.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
from google.cloud import firestore
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from spotframework.engine.playlistengine import PlaylistEngine
|
||||||
|
from spotframework.engine.filter.shuffle import Shuffle
|
||||||
|
from spotframework.engine.filter.sortreversereleasedate import SortReverseReleaseDate
|
||||||
|
from spotframework.engine.filter.deduplicatebyid import DeduplicateByID
|
||||||
|
|
||||||
|
from spotframework.net.network import Network
|
||||||
|
from spotframework.net.user import User
|
||||||
|
|
||||||
|
db = firestore.Client()
|
||||||
|
|
||||||
|
captured_playlists = []
|
||||||
|
|
||||||
|
|
||||||
|
def play_user_playlist(username,
|
||||||
|
playlist_type='default',
|
||||||
|
parts=None,
|
||||||
|
playlists=None,
|
||||||
|
shuffle=False,
|
||||||
|
include_recommendations=True,
|
||||||
|
recommendation_sample=10,
|
||||||
|
day_boundary=10):
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
users = [i for i in db.collection(u'spotify_users').where(u'username', u'==', username).stream()]
|
||||||
|
|
||||||
|
logger.info(f'{username}')
|
||||||
|
|
||||||
|
if len(users) == 1:
|
||||||
|
|
||||||
|
user_dict = users[0].to_dict()
|
||||||
|
|
||||||
|
if not parts and not playlists:
|
||||||
|
logger.critical(f'no playlists to use for creation ({username})')
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(parts) == 0 and len(playlists) == 0:
|
||||||
|
logger.critical(f'no playlists to use for creation ({username})')
|
||||||
|
return
|
||||||
|
|
||||||
|
spotify_keys = db.document('key/spotify').get().to_dict()
|
||||||
|
|
||||||
|
net = Network(User(spotify_keys['clientid'],
|
||||||
|
spotify_keys['clientsecret'],
|
||||||
|
user_dict['access_token'],
|
||||||
|
user_dict['refresh_token']))
|
||||||
|
|
||||||
|
engine = PlaylistEngine(net)
|
||||||
|
engine.load_user_playlists()
|
||||||
|
|
||||||
|
processors = [DeduplicateByID()]
|
||||||
|
|
||||||
|
if shuffle:
|
||||||
|
processors.append(Shuffle())
|
||||||
|
else:
|
||||||
|
processors.append(SortReverseReleaseDate())
|
||||||
|
|
||||||
|
global captured_playlists
|
||||||
|
captured_playlists = []
|
||||||
|
|
||||||
|
if not parts:
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
submit_parts = parts
|
||||||
|
|
||||||
|
for part in playlists:
|
||||||
|
submit_parts += generate_parts(users[0].id, part)
|
||||||
|
|
||||||
|
submit_parts = [i for i in {j for j in submit_parts}]
|
||||||
|
|
||||||
|
if playlist_type == 'recents':
|
||||||
|
boundary_date = datetime.datetime.now() - datetime.timedelta(days=int(day_boundary))
|
||||||
|
tracks = engine.get_recent_playlist(boundary_date,
|
||||||
|
submit_parts,
|
||||||
|
processors,
|
||||||
|
include_recommendations=include_recommendations,
|
||||||
|
recommendation_limit=int(recommendation_sample))
|
||||||
|
else:
|
||||||
|
tracks = engine.make_playlist(submit_parts,
|
||||||
|
processors,
|
||||||
|
include_recommendations=include_recommendations,
|
||||||
|
recommendation_limit=int(recommendation_sample))
|
||||||
|
|
||||||
|
net.play(uris=[i['uri'] for i in tracks])
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.critical(f'multiple/no user(s) found ({username})')
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def generate_parts(user_id, name):
|
||||||
|
|
||||||
|
playlist_doc = [i.to_dict() for i in
|
||||||
|
db.document(u'spotify_users/{}'.format(user_id))
|
||||||
|
.collection(u'playlists')
|
||||||
|
.where(u'name', '==', name).stream()][0]
|
||||||
|
|
||||||
|
return_parts = playlist_doc['parts']
|
||||||
|
|
||||||
|
captured_playlists.append(name)
|
||||||
|
|
||||||
|
for i in playlist_doc['playlist_references']:
|
||||||
|
if i not in captured_playlists:
|
||||||
|
return_parts += generate_parts(user_id, i)
|
||||||
|
|
||||||
|
return return_parts
|
@ -13,7 +13,7 @@ class PlaylistView extends Component{
|
|||||||
playlists: [],
|
playlists: [],
|
||||||
filteredPlaylists: [],
|
filteredPlaylists: [],
|
||||||
playlist_references: [],
|
playlist_references: [],
|
||||||
type: null,
|
type: 'default',
|
||||||
|
|
||||||
day_boundary: '',
|
day_boundary: '',
|
||||||
recommendation_sample: '',
|
recommendation_sample: '',
|
||||||
@ -84,6 +84,9 @@ class PlaylistView extends Component{
|
|||||||
if(event.target.name == 'recommendation_sample'){
|
if(event.target.name == 'recommendation_sample'){
|
||||||
this.handleRecSampleChange(event.target.value);
|
this.handleRecSampleChange(event.target.value);
|
||||||
}
|
}
|
||||||
|
if(event.target.name == 'type'){
|
||||||
|
this.handleTypeChange(event.target.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDayBoundaryChange(boundary) {
|
handleDayBoundaryChange(boundary) {
|
||||||
@ -104,6 +107,15 @@ class PlaylistView extends Component{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTypeChange(sample){
|
||||||
|
axios.post('/api/playlist', {
|
||||||
|
name: this.state.name,
|
||||||
|
type: sample
|
||||||
|
}).catch((error) => {
|
||||||
|
showMessage(`error updating type (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleShuffleChange(event) {
|
handleShuffleChange(event) {
|
||||||
this.setState({
|
this.setState({
|
||||||
shuffle: event.target.checked
|
shuffle: event.target.checked
|
||||||
@ -240,22 +252,26 @@ class PlaylistView extends Component{
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleRun(event){
|
handleRun(event){
|
||||||
axios.get('/api/user')
|
if(this.state.playlist_references.length > 0 || this.state.parts.length > 0){
|
||||||
.then((response) => {
|
axios.get('/api/user')
|
||||||
if(response.data.spotify_linked == true){
|
.then((response) => {
|
||||||
axios.get('/api/playlist/run', {params: {name: this.state.name}})
|
if(response.data.spotify_linked == true){
|
||||||
.then((reponse) => {
|
axios.get('/api/playlist/run', {params: {name: this.state.name}})
|
||||||
showMessage(`${this.state.name} ran`);
|
.then((reponse) => {
|
||||||
})
|
showMessage(`${this.state.name} ran`);
|
||||||
.catch((error) => {
|
})
|
||||||
showMessage(`error running ${this.state.name} (${error.response.status})`);
|
.catch((error) => {
|
||||||
});
|
showMessage(`error running ${this.state.name} (${error.response.status})`);
|
||||||
}else{
|
});
|
||||||
showMessage(`link spotify before running`);
|
}else{
|
||||||
}
|
showMessage(`link spotify before running`);
|
||||||
}).catch((error) => {
|
}
|
||||||
showMessage(`error running ${this.state.name} (${error.response.status})`);
|
}).catch((error) => {
|
||||||
});
|
showMessage(`error running ${this.state.name} (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage(`add either playlists or parts`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
@ -349,6 +365,20 @@ class PlaylistView extends Component{
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
<tr>
|
||||||
|
<td className="center-text ui-text text-no-select">
|
||||||
|
playlist type
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select className="full-width"
|
||||||
|
name="type"
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
value={this.state.type}>
|
||||||
|
<option value="default">default</option>
|
||||||
|
<option value="recents">recents</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{ this.state.type == 'recents' &&
|
{ this.state.type == 'recents' &&
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="2" className="center-text ui-text text-no-select" style={{fontStyle: "italic"}}>
|
<td colSpan="2" className="center-text ui-text text-no-select" style={{fontStyle: "italic"}}>
|
||||||
|
@ -4,6 +4,7 @@ const axios = require('axios');
|
|||||||
|
|
||||||
import PlaylistsView from "./PlaylistsView.js"
|
import PlaylistsView from "./PlaylistsView.js"
|
||||||
import NewPlaylist from "./NewPlaylist.js";
|
import NewPlaylist from "./NewPlaylist.js";
|
||||||
|
import ScratchView from "./ScratchView.js";
|
||||||
|
|
||||||
class Playlists extends Component {
|
class Playlists extends Component {
|
||||||
render(){
|
render(){
|
||||||
@ -11,11 +12,13 @@ class Playlists extends Component {
|
|||||||
<div>
|
<div>
|
||||||
<ul className="navbar" style={{width: "100%"}}>
|
<ul className="navbar" style={{width: "100%"}}>
|
||||||
<li><Link to={`${this.props.match.url}/new`}>new</Link></li>
|
<li><Link to={`${this.props.match.url}/new`}>new</Link></li>
|
||||||
|
<li><Link to={`${this.props.match.url}/play`}>play</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path={`${this.props.match.url}/`} component={PlaylistsView} />
|
<Route exact path={`${this.props.match.url}/`} component={PlaylistsView} />
|
||||||
<Route path={`${this.props.match.url}/new`} component={NewPlaylist} />
|
<Route path={`${this.props.match.url}/new`} component={NewPlaylist} />
|
||||||
|
<Route path={`${this.props.match.url}/play`} component={ScratchView} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
354
src/js/Playlist/ScratchView.js
Normal file
354
src/js/Playlist/ScratchView.js
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
import showMessage from "../Toast.js"
|
||||||
|
|
||||||
|
class ScratchView extends Component{
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
name: 'play',
|
||||||
|
parts: [],
|
||||||
|
playlists: [],
|
||||||
|
filteredPlaylists: [],
|
||||||
|
playlist_references: [],
|
||||||
|
type: 'default',
|
||||||
|
|
||||||
|
day_boundary: 5,
|
||||||
|
recommendation_sample: 5,
|
||||||
|
newPlaylistName: '',
|
||||||
|
newPlaylistReference: '',
|
||||||
|
|
||||||
|
shuffle: false,
|
||||||
|
include_recommendations: false
|
||||||
|
}
|
||||||
|
this.handleAddPart = this.handleAddPart.bind(this);
|
||||||
|
this.handleAddReference = this.handleAddReference.bind(this);
|
||||||
|
this.handleInputChange = this.handleInputChange.bind(this);
|
||||||
|
this.handleRemoveRow = this.handleRemoveRow.bind(this);
|
||||||
|
this.handleRemoveRefRow = this.handleRemoveRefRow.bind(this);
|
||||||
|
|
||||||
|
this.handleRun = this.handleRun.bind(this);
|
||||||
|
|
||||||
|
this.handleShuffleChange = this.handleShuffleChange.bind(this);
|
||||||
|
this.handleRecChange = this.handleRecChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
|
||||||
|
this.getPlaylists();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getPlaylists(){
|
||||||
|
return axios.get(`/api/playlists`)
|
||||||
|
.then((response) => {
|
||||||
|
var filteredPlaylists = response.data.playlists.filter((entry) => entry.name != this.state.name);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
playlists: response.data.playlists,
|
||||||
|
newPlaylistReference: filteredPlaylists.length > 0 ? filteredPlaylists[0].name : ''
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showMessage(`error getting playlists (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange(event){
|
||||||
|
this.setState({
|
||||||
|
[event.target.name]: event.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTypeChange(sample){
|
||||||
|
axios.post('/api/playlist', {
|
||||||
|
name: this.state.name,
|
||||||
|
type: sample
|
||||||
|
}).catch((error) => {
|
||||||
|
showMessage(`error updating type (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleShuffleChange(event) {
|
||||||
|
this.setState({
|
||||||
|
shuffle: event.target.checked
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRecChange(event) {
|
||||||
|
this.setState({
|
||||||
|
include_recommendations: event.target.checked
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddPart(event){
|
||||||
|
|
||||||
|
if(this.state.newPlaylistName.length != 0){
|
||||||
|
|
||||||
|
var check = this.state.parts.includes(this.state.newPlaylistName);
|
||||||
|
|
||||||
|
if(check == false) {
|
||||||
|
var parts = this.state.parts.slice();
|
||||||
|
parts.push(this.state.newPlaylistName);
|
||||||
|
|
||||||
|
parts.sort(function(a, b){
|
||||||
|
if(a < b) { return -1; }
|
||||||
|
if(a > b) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
parts: parts,
|
||||||
|
newPlaylistName: ''
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage('playlist already added');
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
showMessage('enter playlist name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddReference(event){
|
||||||
|
|
||||||
|
if(this.state.newPlaylistReference.length != 0){
|
||||||
|
|
||||||
|
var check = this.state.playlist_references.includes(this.state.newPlaylistReference);
|
||||||
|
|
||||||
|
if(check == false) {
|
||||||
|
var playlist_references = this.state.playlist_references.slice();
|
||||||
|
playlist_references.push(this.state.newPlaylistReference);
|
||||||
|
|
||||||
|
playlist_references.sort(function(a, b){
|
||||||
|
if(a < b) { return -1; }
|
||||||
|
if(a > b) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
var filteredPlaylists = this.state.playlists.filter((entry) => entry.name != this.state.name);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
playlist_references: playlist_references,
|
||||||
|
newPlaylistReference: filteredPlaylists.length > 0 ? filteredPlaylists[0].name : ''
|
||||||
|
});
|
||||||
|
|
||||||
|
}else{
|
||||||
|
showMessage('playlist already added');
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
showMessage('no other playlists to add');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRemoveRow(id, event){
|
||||||
|
var parts = this.state.parts;
|
||||||
|
parts = parts.filter(e => e !== id);
|
||||||
|
this.setState({
|
||||||
|
parts: parts
|
||||||
|
});
|
||||||
|
|
||||||
|
if(parts.length == 0) {
|
||||||
|
parts = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRemoveRefRow(id, event){
|
||||||
|
var playlist_references = this.state.playlist_references;
|
||||||
|
playlist_references = playlist_references.filter(e => e !== id);
|
||||||
|
this.setState({
|
||||||
|
playlist_references: playlist_references
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRun(event){
|
||||||
|
if(this.state.playlist_references.length > 0 || this.state.parts.length > 0){
|
||||||
|
axios.get('/api/user')
|
||||||
|
.then((response) => {
|
||||||
|
if(response.data.spotify_linked == true){
|
||||||
|
axios.post('/api/playlist/play', {
|
||||||
|
parts: this.state.parts,
|
||||||
|
playlists: this.state.playlist_references,
|
||||||
|
shuffle: this.state.shuffle,
|
||||||
|
include_recommendations: this.state.include_recommendations,
|
||||||
|
recommendation_sample: this.state.recommendation_sample,
|
||||||
|
day_boundary: this.state.day_boundary,
|
||||||
|
playlist_type: this.state.type
|
||||||
|
})
|
||||||
|
.then((reponse) => {
|
||||||
|
showMessage(`played`);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showMessage(`error playing (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage(`link spotify before running`);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
showMessage(`error playing (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage(`add either playlists or parts`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
|
||||||
|
const table = (
|
||||||
|
<table className="app-table max-width">
|
||||||
|
{/* <thead>
|
||||||
|
<tr>
|
||||||
|
<th colSpan="2"><h1 className="text-no-select">{ this.state.name }</h1></th>
|
||||||
|
</tr>
|
||||||
|
</thead> */}
|
||||||
|
{ this.state.playlist_references.length > 0 && <ListBlock name="managed" handler={this.handleRemoveRefRow} list={this.state.playlist_references}/> }
|
||||||
|
{ this.state.parts.length > 0 && <ListBlock name="spotify" handler={this.handleRemoveRow} list={this.state.parts}/> }
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2" className="center-text ui-text text-no-select" style={{fontStyle: "italic"}}>
|
||||||
|
<br></br>spotify playlist can be the name of either your own created playlist or one you follow, names are case sensitive
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="text"
|
||||||
|
name="newPlaylistName"
|
||||||
|
className="full-width"
|
||||||
|
value={this.state.newPlaylistName}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
placeholder="spotify playlist name"></input>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button className="button full-width" onClick={this.handleAddPart}>add</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<select name="newPlaylistReference"
|
||||||
|
className="full-width"
|
||||||
|
value={this.state.newPlaylistReference}
|
||||||
|
onChange={this.handleInputChange}>
|
||||||
|
{ this.state.playlists
|
||||||
|
.filter((entry) => entry.name != this.state.name)
|
||||||
|
.map((entry) => <ReferenceEntry name={entry.name} key={entry.name} />) }
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button className="button full-width" onClick={this.handleAddReference}>add</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="center-text ui-text text-no-select">
|
||||||
|
shuffle output?
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="shuffle"
|
||||||
|
checked={this.state.shuffle}
|
||||||
|
onChange={this.handleShuffleChange}></input>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="center-text ui-text text-no-select">
|
||||||
|
include recommendations?
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="include_recommendations"
|
||||||
|
checked={this.state.include_recommendations}
|
||||||
|
onChange={this.handleRecChange}></input>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="center-text ui-text text-no-select">
|
||||||
|
number of recommendations
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="number"
|
||||||
|
name="recommendation_sample"
|
||||||
|
className="full-width"
|
||||||
|
value={this.state.recommendation_sample}
|
||||||
|
onChange={this.handleInputChange}></input>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{ this.state.type == 'recents' &&
|
||||||
|
<tr>
|
||||||
|
<td className="center-text ui-text text-no-select">
|
||||||
|
added since (days)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="number"
|
||||||
|
name="day_boundary"
|
||||||
|
className="full-width"
|
||||||
|
value={this.state.day_boundary}
|
||||||
|
onChange={this.handleInputChange}></input>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
<tr>
|
||||||
|
<td className="center-text ui-text text-no-select">
|
||||||
|
playlist type
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select className="full-width"
|
||||||
|
name="type"
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
value={this.state.type}>
|
||||||
|
<option value="default">default</option>
|
||||||
|
<option value="recents">recents</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{ this.state.type == 'recents' &&
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2" className="center-text ui-text text-no-select" style={{fontStyle: "italic"}}>
|
||||||
|
<br></br>'recents' playlists search for and include this months and last months playlists when named in the format
|
||||||
|
<br></br>[month] [year]
|
||||||
|
<br></br>e.g july 19 (lowercase)
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2">
|
||||||
|
<button className="button full-width" onClick={this.handleRun}>play</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
|
||||||
|
const error = <p style={{textAlign: "center"}}>{ this.state.error_text }</p>;
|
||||||
|
|
||||||
|
return this.state.error ? error : table;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReferenceEntry(props) {
|
||||||
|
return (
|
||||||
|
<option value={props.name}>{props.name}</option>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListBlock(props) {
|
||||||
|
return (
|
||||||
|
<tbody>
|
||||||
|
<tr><td colSpan="2" className="ui-text center-text text-no-select" style={{fontStyle: 'italic'}}>{props.name}</td></tr>
|
||||||
|
{ props.list.map((part) => <Row part={ part } key={ part } handler={props.handler}/>) }
|
||||||
|
</tbody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Row (props) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td className="ui-text center-text text-no-select">{ props.part }</td>
|
||||||
|
<td><button className="ui-text center-text button full-width" onClick={(e) => props.handler(props.part, e)}>remove</button></td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScratchView;
|
Loading…
Reference in New Issue
Block a user