added tag js, added playlist name sorting, moved cloud tasks
This commit is contained in:
parent
97ffc1f141
commit
29bd875ecd
36
deploy
36
deploy
@ -22,14 +22,34 @@ cp -r spotfm/spotfm $stage_dir/
|
|||||||
|
|
||||||
cd $stage_dir
|
cd $stage_dir
|
||||||
|
|
||||||
echo '>> building css'
|
|
||||||
sass --style=compressed src/scss/style.scss build/style.css
|
|
||||||
|
|
||||||
echo '>> building javascript'
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
echo '>> deploying'
|
|
||||||
gcloud config set project sarsooxyz
|
gcloud config set project sarsooxyz
|
||||||
gcloud app deploy
|
|
||||||
|
echo '>>> Target?'
|
||||||
|
echo ''
|
||||||
|
echo '(0) > api'
|
||||||
|
echo '(1) > update_tag'
|
||||||
|
read deploy_target
|
||||||
|
|
||||||
|
|
||||||
|
case "$deploy_target" in
|
||||||
|
0)
|
||||||
|
echo '>> building css'
|
||||||
|
sass --style=compressed src/scss/style.scss build/style.css
|
||||||
|
|
||||||
|
echo '>> building javascript'
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo '>> deploying'
|
||||||
|
gcloud app deploy
|
||||||
|
;;
|
||||||
|
|
||||||
|
1)
|
||||||
|
echo '>> deploying update_tag'
|
||||||
|
gcloud functions deploy update_tag
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
138
music/api/api.py
138
music/api/api.py
@ -1,15 +1,14 @@
|
|||||||
from flask import Blueprint, session, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import datetime
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from google.cloud import firestore
|
from google.cloud import firestore
|
||||||
from google.cloud import tasks_v2
|
|
||||||
from google.protobuf import timestamp_pb2
|
|
||||||
|
|
||||||
from music.api.decorators import login_required, login_or_basic_auth, admin_required, gae_cron, cloud_task
|
from music.api.decorators import login_required, login_or_basic_auth, admin_required, gae_cron, cloud_task
|
||||||
|
from music.cloud.tasks import execute_all_user_playlists, execute_user_playlists, create_run_user_playlist_task, \
|
||||||
|
create_play_user_playlist_task
|
||||||
from music.tasks.run_user_playlist import run_user_playlist as run_user_playlist
|
from music.tasks.run_user_playlist import run_user_playlist as run_user_playlist
|
||||||
from music.tasks.play_user_playlist import play_user_playlist as play_user_playlist
|
from music.tasks.play_user_playlist import play_user_playlist as play_user_playlist
|
||||||
|
|
||||||
@ -18,9 +17,6 @@ import music.db.database as database
|
|||||||
blueprint = Blueprint('api', __name__)
|
blueprint = Blueprint('api', __name__)
|
||||||
db = firestore.Client()
|
db = firestore.Client()
|
||||||
|
|
||||||
tasker = tasks_v2.CloudTasksClient()
|
|
||||||
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -411,12 +407,12 @@ def run_playlist_task():
|
|||||||
def run_user(username=None):
|
def run_user(username=None):
|
||||||
|
|
||||||
db_user = database.get_user(username)
|
db_user = database.get_user(username)
|
||||||
if db_user.type == db_user.Type.admin:
|
if db_user.user_type == db_user.Type.admin:
|
||||||
user_name = request.args.get('username', username)
|
user_name = request.args.get('username', username)
|
||||||
else:
|
else:
|
||||||
user_name = username
|
user_name = username
|
||||||
|
|
||||||
execute_user(user_name)
|
execute_user_playlists(user_name)
|
||||||
|
|
||||||
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
||||||
|
|
||||||
@ -427,7 +423,7 @@ def run_user_task():
|
|||||||
|
|
||||||
payload = request.get_data(as_text=True)
|
payload = request.get_data(as_text=True)
|
||||||
if payload:
|
if payload:
|
||||||
execute_user(payload)
|
execute_user_playlists(payload)
|
||||||
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
||||||
|
|
||||||
|
|
||||||
@ -436,7 +432,7 @@ def run_user_task():
|
|||||||
@admin_required
|
@admin_required
|
||||||
def run_users(username=None):
|
def run_users(username=None):
|
||||||
|
|
||||||
execute_all_users()
|
execute_all_user_playlists()
|
||||||
return jsonify({'message': 'executed all users', 'status': 'success'}), 200
|
return jsonify({'message': 'executed all users', 'status': 'success'}), 200
|
||||||
|
|
||||||
|
|
||||||
@ -444,123 +440,5 @@ def run_users(username=None):
|
|||||||
@gae_cron
|
@gae_cron
|
||||||
def run_users_cron():
|
def run_users_cron():
|
||||||
|
|
||||||
execute_all_users()
|
execute_all_user_playlists()
|
||||||
return jsonify({'status': 'success'}), 200
|
return jsonify({'status': 'success'}), 200
|
||||||
|
|
||||||
|
|
||||||
def execute_all_users():
|
|
||||||
|
|
||||||
seconds_delay = 0
|
|
||||||
logger.info('running')
|
|
||||||
|
|
||||||
for iter_user in database.get_users():
|
|
||||||
|
|
||||||
if iter_user.spotify_linked and not iter_user.locked:
|
|
||||||
|
|
||||||
task = {
|
|
||||||
'app_engine_http_request': { # Specify the type of request.
|
|
||||||
'http_method': 'POST',
|
|
||||||
'relative_uri': '/api/playlist/run/user/task',
|
|
||||||
'body': iter_user.username.encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds_delay)
|
|
||||||
|
|
||||||
timestamp = timestamp_pb2.Timestamp()
|
|
||||||
timestamp.FromDatetime(d)
|
|
||||||
|
|
||||||
task['schedule_time'] = timestamp
|
|
||||||
|
|
||||||
tasker.create_task(task_path, task)
|
|
||||||
seconds_delay += 30
|
|
||||||
|
|
||||||
|
|
||||||
def execute_user(username):
|
|
||||||
|
|
||||||
playlists = database.get_user_playlists(username)
|
|
||||||
|
|
||||||
seconds_delay = 0
|
|
||||||
logger.info(f'running {username}')
|
|
||||||
|
|
||||||
for iterate_playlist in playlists:
|
|
||||||
if iterate_playlist.uri:
|
|
||||||
|
|
||||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
|
||||||
create_run_user_playlist_task(username, iterate_playlist.name, seconds_delay)
|
|
||||||
else:
|
|
||||||
run_playlist(username, iterate_playlist.name)
|
|
||||||
|
|
||||||
seconds_delay += 6
|
|
||||||
|
|
||||||
|
|
||||||
def create_run_user_playlist_task(username, playlist_name, delay=0):
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 create_play_user_playlist_task(username,
|
|
||||||
parts=None,
|
|
||||||
playlist_type='default',
|
|
||||||
playlists=None,
|
|
||||||
shuffle=False,
|
|
||||||
include_recommendations=False,
|
|
||||||
recommendation_sample=10,
|
|
||||||
day_boundary=10,
|
|
||||||
add_this_month=False,
|
|
||||||
add_last_month=False,
|
|
||||||
delay=0,
|
|
||||||
device_name=None):
|
|
||||||
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,
|
|
||||||
'add_this_month': add_this_month,
|
|
||||||
'add_last_month': add_last_month,
|
|
||||||
'device_name': device_name
|
|
||||||
}).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)
|
|
||||||
|
@ -2,10 +2,10 @@ from flask import Blueprint, jsonify, request
|
|||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import datetime
|
|
||||||
|
|
||||||
from music.api.decorators import admin_required, login_or_basic_auth, lastfm_username_required, spotify_link_required, cloud_task, gae_cron
|
from music.api.decorators import admin_required, login_or_basic_auth, lastfm_username_required, spotify_link_required, cloud_task, gae_cron
|
||||||
import music.db.database as database
|
import music.db.database as database
|
||||||
|
from music.cloud.tasks import execute_all_user_playlist_stats, execute_user_playlist_stats, create_refresh_playlist_task
|
||||||
from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats, \
|
from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats, \
|
||||||
refresh_lastfm_album_stats, \
|
refresh_lastfm_album_stats, \
|
||||||
refresh_lastfm_artist_stats
|
refresh_lastfm_artist_stats
|
||||||
@ -13,17 +13,9 @@ from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats, \
|
|||||||
from spotfm.maths.counter import Counter
|
from spotfm.maths.counter import Counter
|
||||||
from spotframework.model.uri import Uri
|
from spotframework.model.uri import Uri
|
||||||
|
|
||||||
from google.cloud import firestore
|
|
||||||
from google.cloud import tasks_v2
|
|
||||||
from google.protobuf import timestamp_pb2
|
|
||||||
|
|
||||||
blueprint = Blueprint('spotfm-api', __name__)
|
blueprint = Blueprint('spotfm-api', __name__)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
db = firestore.Client()
|
|
||||||
tasker = tasks_v2.CloudTasksClient()
|
|
||||||
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/count', methods=['GET'])
|
@blueprint.route('/count', methods=['GET'])
|
||||||
@login_or_basic_auth
|
@login_or_basic_auth
|
||||||
@ -144,14 +136,14 @@ def run_playlist_artist_task():
|
|||||||
@login_or_basic_auth
|
@login_or_basic_auth
|
||||||
@admin_required
|
@admin_required
|
||||||
def run_users(username=None):
|
def run_users(username=None):
|
||||||
execute_all_users()
|
execute_all_user_playlist_stats()
|
||||||
return jsonify({'message': 'executed all users', 'status': 'success'}), 200
|
return jsonify({'message': 'executed all users', 'status': 'success'}), 200
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/playlist/refresh/users/cron', methods=['GET'])
|
@blueprint.route('/playlist/refresh/users/cron', methods=['GET'])
|
||||||
@gae_cron
|
@gae_cron
|
||||||
def run_users_task():
|
def run_users_task():
|
||||||
execute_all_users()
|
execute_all_user_playlist_stats()
|
||||||
return jsonify({'status': 'success'}), 200
|
return jsonify({'status': 'success'}), 200
|
||||||
|
|
||||||
|
|
||||||
@ -165,7 +157,7 @@ def run_user(username=None):
|
|||||||
else:
|
else:
|
||||||
user_name = username
|
user_name = username
|
||||||
|
|
||||||
execute_user(user_name)
|
execute_user_playlist_stats(user_name)
|
||||||
|
|
||||||
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
||||||
|
|
||||||
@ -176,125 +168,5 @@ def run_user_task():
|
|||||||
|
|
||||||
payload = request.get_data(as_text=True)
|
payload = request.get_data(as_text=True)
|
||||||
if payload:
|
if payload:
|
||||||
execute_user(payload)
|
execute_user_playlist_stats(payload)
|
||||||
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
||||||
|
|
||||||
|
|
||||||
def execute_all_users():
|
|
||||||
|
|
||||||
seconds_delay = 0
|
|
||||||
logger.info('running')
|
|
||||||
|
|
||||||
for iter_user in database.get_users():
|
|
||||||
|
|
||||||
if iter_user.spotify_linked and iter_user.lastfm_username and \
|
|
||||||
len(iter_user.lastfm_username) > 0 and not iter_user.locked:
|
|
||||||
|
|
||||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
|
||||||
create_refresh_user_task(username=iter_user.username, delay=seconds_delay)
|
|
||||||
else:
|
|
||||||
execute_user(username=iter_user.username)
|
|
||||||
|
|
||||||
seconds_delay += 2400
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.debug(f'skipping {iter_user.username}')
|
|
||||||
|
|
||||||
|
|
||||||
def execute_user(username):
|
|
||||||
|
|
||||||
playlists = database.get_user_playlists(username)
|
|
||||||
user = database.get_user(username)
|
|
||||||
|
|
||||||
seconds_delay = 0
|
|
||||||
logger.info(f'running {username}')
|
|
||||||
|
|
||||||
if user.lastfm_username and len(user.lastfm_username) > 0:
|
|
||||||
for playlist in playlists:
|
|
||||||
if playlist.uri:
|
|
||||||
|
|
||||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
|
||||||
create_refresh_playlist_task(username, playlist.name, seconds_delay)
|
|
||||||
else:
|
|
||||||
refresh_lastfm_track_stats(username, playlist.name)
|
|
||||||
|
|
||||||
seconds_delay += 1200
|
|
||||||
else:
|
|
||||||
logger.error('no last.fm username')
|
|
||||||
|
|
||||||
|
|
||||||
def create_refresh_user_task(username, delay=0):
|
|
||||||
|
|
||||||
task = {
|
|
||||||
'app_engine_http_request': { # Specify the type of request.
|
|
||||||
'http_method': 'POST',
|
|
||||||
'relative_uri': '/api/spotfm/playlist/refresh/user/task',
|
|
||||||
'body': username.encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if delay > 0:
|
|
||||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
|
||||||
|
|
||||||
timestamp = timestamp_pb2.Timestamp()
|
|
||||||
timestamp.FromDatetime(d)
|
|
||||||
|
|
||||||
task['schedule_time'] = timestamp
|
|
||||||
|
|
||||||
tasker.create_task(task_path, task)
|
|
||||||
|
|
||||||
|
|
||||||
def create_refresh_playlist_task(username, playlist_name, delay=0):
|
|
||||||
|
|
||||||
track_task = {
|
|
||||||
'app_engine_http_request': { # Specify the type of request.
|
|
||||||
'http_method': 'POST',
|
|
||||||
'relative_uri': '/api/spotfm/playlist/refresh/task/track',
|
|
||||||
'body': json.dumps({
|
|
||||||
'username': username,
|
|
||||||
'name': playlist_name
|
|
||||||
}).encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if delay > 0:
|
|
||||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
|
||||||
timestamp = timestamp_pb2.Timestamp()
|
|
||||||
timestamp.FromDatetime(d)
|
|
||||||
|
|
||||||
track_task['schedule_time'] = timestamp
|
|
||||||
|
|
||||||
album_task = {
|
|
||||||
'app_engine_http_request': { # Specify the type of request.
|
|
||||||
'http_method': 'POST',
|
|
||||||
'relative_uri': '/api/spotfm/playlist/refresh/task/album',
|
|
||||||
'body': json.dumps({
|
|
||||||
'username': username,
|
|
||||||
'name': playlist_name
|
|
||||||
}).encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay + 180)
|
|
||||||
timestamp = timestamp_pb2.Timestamp()
|
|
||||||
timestamp.FromDatetime(d)
|
|
||||||
|
|
||||||
album_task['schedule_time'] = timestamp
|
|
||||||
|
|
||||||
artist_task = {
|
|
||||||
'app_engine_http_request': { # Specify the type of request.
|
|
||||||
'http_method': 'POST',
|
|
||||||
'relative_uri': '/api/spotfm/playlist/refresh/task/artist',
|
|
||||||
'body': json.dumps({
|
|
||||||
'username': username,
|
|
||||||
'name': playlist_name
|
|
||||||
}).encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay + 360)
|
|
||||||
timestamp = timestamp_pb2.Timestamp()
|
|
||||||
timestamp.FromDatetime(d)
|
|
||||||
|
|
||||||
artist_task['schedule_time'] = timestamp
|
|
||||||
|
|
||||||
tasker.create_task(task_path, track_task)
|
|
||||||
tasker.create_task(task_path, album_task)
|
|
||||||
tasker.create_task(task_path, artist_task)
|
|
||||||
|
@ -2,16 +2,13 @@ from flask import Blueprint, jsonify, request
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from google.cloud import pubsub_v1
|
|
||||||
|
|
||||||
import music.db.database as database
|
import music.db.database as database
|
||||||
from music.api.decorators import login_or_basic_auth
|
from music.api.decorators import login_or_basic_auth
|
||||||
|
from music.cloud.function import update_tag
|
||||||
|
|
||||||
blueprint = Blueprint('task', __name__)
|
blueprint = Blueprint('task', __name__)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
publisher = pubsub_v1.PublisherClient()
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/tag', methods=['GET'])
|
@blueprint.route('/tag', methods=['GET'])
|
||||||
@login_or_basic_auth
|
@login_or_basic_auth
|
||||||
@ -26,7 +23,7 @@ def tags(username=None):
|
|||||||
@login_or_basic_auth
|
@login_or_basic_auth
|
||||||
def tag(tag_id, username=None):
|
def tag(tag_id, username=None):
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return put_tag(tag_id, username)
|
return get_tag(tag_id, username)
|
||||||
elif request.method == 'PUT':
|
elif request.method == 'PUT':
|
||||||
return put_tag(tag_id, username)
|
return put_tag(tag_id, username)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
@ -63,7 +60,7 @@ def put_tag(tag_id, username):
|
|||||||
update_required = False
|
update_required = False
|
||||||
|
|
||||||
tracks = []
|
tracks = []
|
||||||
if request_json.get('tracks'):
|
if request_json.get('tracks') is not None:
|
||||||
update_required = True
|
update_required = True
|
||||||
for track in request_json['tracks']:
|
for track in request_json['tracks']:
|
||||||
if track.get('name') and track.get('artist'):
|
if track.get('name') and track.get('artist'):
|
||||||
@ -74,7 +71,7 @@ def put_tag(tag_id, username):
|
|||||||
db_tag.tracks = tracks
|
db_tag.tracks = tracks
|
||||||
|
|
||||||
albums = []
|
albums = []
|
||||||
if request_json.get('albums'):
|
if request_json.get('albums') is not None:
|
||||||
update_required = True
|
update_required = True
|
||||||
for album in request_json['albums']:
|
for album in request_json['albums']:
|
||||||
if album.get('name') and album.get('artist'):
|
if album.get('name') and album.get('artist'):
|
||||||
@ -82,13 +79,13 @@ def put_tag(tag_id, username):
|
|||||||
'name': album['name'],
|
'name': album['name'],
|
||||||
'artist': album['artist']
|
'artist': album['artist']
|
||||||
})
|
})
|
||||||
db_tag.album = albums
|
db_tag.albums = albums
|
||||||
|
|
||||||
artists = []
|
artists = []
|
||||||
if request_json.get('artists'):
|
if request_json.get('artists') is not None:
|
||||||
update_required = True
|
update_required = True
|
||||||
for artist in request_json['tracks']:
|
for artist in request_json['artists']:
|
||||||
if artist.get('name') and artist.get('artist'):
|
if artist.get('name'):
|
||||||
artists.append({
|
artists.append({
|
||||||
'name': artist['name']
|
'name': artist['name']
|
||||||
})
|
})
|
||||||
@ -103,11 +100,10 @@ def put_tag(tag_id, username):
|
|||||||
def post_tag(tag_id, username):
|
def post_tag(tag_id, username):
|
||||||
logger.info(f'creating {tag_id} for {username}')
|
logger.info(f'creating {tag_id} for {username}')
|
||||||
|
|
||||||
new_tag = database.create_tag(username=username, tag_id=tag_id)
|
tag_id = tag_id.replace(' ', '_')
|
||||||
if new_tag is not None:
|
|
||||||
|
database.create_tag(username=username, tag_id=tag_id)
|
||||||
return jsonify({"message": 'tag added', "status": "success"}), 201
|
return jsonify({"message": 'tag added', "status": "success"}), 201
|
||||||
else:
|
|
||||||
return jsonify({"error": 'tag not created'}), 400
|
|
||||||
|
|
||||||
|
|
||||||
def delete_tag(tag_id, username):
|
def delete_tag(tag_id, username):
|
||||||
@ -121,7 +117,9 @@ def delete_tag(tag_id, username):
|
|||||||
return jsonify({"error": 'tag not deleted'}), 400
|
return jsonify({"error": 'tag not deleted'}), 400
|
||||||
|
|
||||||
|
|
||||||
def update_tag(username, tag_id):
|
@blueprint.route('/tag/<tag_id>/update', methods=['GET'])
|
||||||
logger.info(f'queuing {tag_id} update for {username}')
|
@login_or_basic_auth
|
||||||
|
def tag_refresh(tag_id, username=None):
|
||||||
publisher.publish('projects/sarsooxyz/topics/update_tag', b'', tag_id=tag_id, username=username)
|
logger.info(f'updating {tag_id} tag for {username}')
|
||||||
|
update_tag(username=username, tag_id=tag_id)
|
||||||
|
return jsonify({"message": 'tag updated', "status": "success"}), 200
|
||||||
|
0
music/cloud/__init__.py
Normal file
0
music/cloud/__init__.py
Normal file
10
music/cloud/function.py
Normal file
10
music/cloud/function.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import logging
|
||||||
|
from google.cloud import pubsub_v1
|
||||||
|
|
||||||
|
publisher = pubsub_v1.PublisherClient()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def update_tag(username, tag_id):
|
||||||
|
logger.info(f'queuing {tag_id} update for {username}')
|
||||||
|
publisher.publish('projects/sarsooxyz/topics/update_tag', b'', tag_id=tag_id, username=username)
|
254
music/cloud/tasks.py
Normal file
254
music/cloud/tasks.py
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from google.cloud import tasks_v2
|
||||||
|
from google.protobuf import timestamp_pb2
|
||||||
|
|
||||||
|
from music.db import database as database
|
||||||
|
from music.tasks.run_user_playlist import run_user_playlist
|
||||||
|
from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats
|
||||||
|
|
||||||
|
tasker = tasks_v2.CloudTasksClient()
|
||||||
|
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_all_user_playlists():
|
||||||
|
|
||||||
|
seconds_delay = 0
|
||||||
|
logger.info('running')
|
||||||
|
|
||||||
|
for iter_user in database.get_users():
|
||||||
|
|
||||||
|
if iter_user.spotify_linked and not iter_user.locked:
|
||||||
|
|
||||||
|
task = {
|
||||||
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
|
'http_method': 'POST',
|
||||||
|
'relative_uri': '/api/playlist/run/user/task',
|
||||||
|
'body': iter_user.username.encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds_delay)
|
||||||
|
|
||||||
|
timestamp = timestamp_pb2.Timestamp()
|
||||||
|
timestamp.FromDatetime(d)
|
||||||
|
|
||||||
|
task['schedule_time'] = timestamp
|
||||||
|
|
||||||
|
tasker.create_task(task_path, task)
|
||||||
|
seconds_delay += 30
|
||||||
|
|
||||||
|
|
||||||
|
def execute_user_playlists(username):
|
||||||
|
|
||||||
|
playlists = database.get_user_playlists(username)
|
||||||
|
|
||||||
|
seconds_delay = 0
|
||||||
|
logger.info(f'running {username}')
|
||||||
|
|
||||||
|
for iterate_playlist in playlists:
|
||||||
|
if iterate_playlist.uri:
|
||||||
|
|
||||||
|
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||||
|
create_run_user_playlist_task(username, iterate_playlist.name, seconds_delay)
|
||||||
|
else:
|
||||||
|
run_user_playlist(username, iterate_playlist.name)
|
||||||
|
|
||||||
|
seconds_delay += 6
|
||||||
|
|
||||||
|
|
||||||
|
def create_run_user_playlist_task(username, playlist_name, delay=0):
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 create_play_user_playlist_task(username,
|
||||||
|
parts=None,
|
||||||
|
playlist_type='default',
|
||||||
|
playlists=None,
|
||||||
|
shuffle=False,
|
||||||
|
include_recommendations=False,
|
||||||
|
recommendation_sample=10,
|
||||||
|
day_boundary=10,
|
||||||
|
add_this_month=False,
|
||||||
|
add_last_month=False,
|
||||||
|
delay=0,
|
||||||
|
device_name=None):
|
||||||
|
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,
|
||||||
|
'add_this_month': add_this_month,
|
||||||
|
'add_last_month': add_last_month,
|
||||||
|
'device_name': device_name
|
||||||
|
}).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 execute_all_user_playlist_stats():
|
||||||
|
|
||||||
|
seconds_delay = 0
|
||||||
|
logger.info('running')
|
||||||
|
|
||||||
|
for iter_user in database.get_users():
|
||||||
|
|
||||||
|
if iter_user.spotify_linked and iter_user.lastfm_username and \
|
||||||
|
len(iter_user.lastfm_username) > 0 and not iter_user.locked:
|
||||||
|
|
||||||
|
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||||
|
create_refresh_user_task(username=iter_user.username, delay=seconds_delay)
|
||||||
|
else:
|
||||||
|
execute_user_playlist_stats(username=iter_user.username)
|
||||||
|
|
||||||
|
seconds_delay += 2400
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.debug(f'skipping {iter_user.username}')
|
||||||
|
|
||||||
|
|
||||||
|
def execute_user_playlist_stats(username):
|
||||||
|
|
||||||
|
playlists = database.get_user_playlists(username)
|
||||||
|
user = database.get_user(username)
|
||||||
|
|
||||||
|
seconds_delay = 0
|
||||||
|
logger.info(f'running {username}')
|
||||||
|
|
||||||
|
if user.lastfm_username and len(user.lastfm_username) > 0:
|
||||||
|
for playlist in playlists:
|
||||||
|
if playlist.uri:
|
||||||
|
|
||||||
|
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||||
|
create_refresh_playlist_task(username, playlist.name, seconds_delay)
|
||||||
|
else:
|
||||||
|
refresh_lastfm_track_stats(username, playlist.name)
|
||||||
|
|
||||||
|
seconds_delay += 1200
|
||||||
|
else:
|
||||||
|
logger.error('no last.fm username')
|
||||||
|
|
||||||
|
|
||||||
|
def create_refresh_user_task(username, delay=0):
|
||||||
|
|
||||||
|
task = {
|
||||||
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
|
'http_method': 'POST',
|
||||||
|
'relative_uri': '/api/spotfm/playlist/refresh/user/task',
|
||||||
|
'body': username.encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if delay > 0:
|
||||||
|
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
||||||
|
|
||||||
|
timestamp = timestamp_pb2.Timestamp()
|
||||||
|
timestamp.FromDatetime(d)
|
||||||
|
|
||||||
|
task['schedule_time'] = timestamp
|
||||||
|
|
||||||
|
tasker.create_task(task_path, task)
|
||||||
|
|
||||||
|
|
||||||
|
def create_refresh_playlist_task(username, playlist_name, delay=0):
|
||||||
|
|
||||||
|
track_task = {
|
||||||
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
|
'http_method': 'POST',
|
||||||
|
'relative_uri': '/api/spotfm/playlist/refresh/task/track',
|
||||||
|
'body': json.dumps({
|
||||||
|
'username': username,
|
||||||
|
'name': playlist_name
|
||||||
|
}).encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if delay > 0:
|
||||||
|
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
||||||
|
timestamp = timestamp_pb2.Timestamp()
|
||||||
|
timestamp.FromDatetime(d)
|
||||||
|
|
||||||
|
track_task['schedule_time'] = timestamp
|
||||||
|
|
||||||
|
album_task = {
|
||||||
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
|
'http_method': 'POST',
|
||||||
|
'relative_uri': '/api/spotfm/playlist/refresh/task/album',
|
||||||
|
'body': json.dumps({
|
||||||
|
'username': username,
|
||||||
|
'name': playlist_name
|
||||||
|
}).encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay + 180)
|
||||||
|
timestamp = timestamp_pb2.Timestamp()
|
||||||
|
timestamp.FromDatetime(d)
|
||||||
|
|
||||||
|
album_task['schedule_time'] = timestamp
|
||||||
|
|
||||||
|
artist_task = {
|
||||||
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
|
'http_method': 'POST',
|
||||||
|
'relative_uri': '/api/spotfm/playlist/refresh/task/artist',
|
||||||
|
'body': json.dumps({
|
||||||
|
'username': username,
|
||||||
|
'name': playlist_name
|
||||||
|
}).encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay + 360)
|
||||||
|
timestamp = timestamp_pb2.Timestamp()
|
||||||
|
timestamp.FromDatetime(d)
|
||||||
|
|
||||||
|
artist_task['schedule_time'] = timestamp
|
||||||
|
|
||||||
|
tasker.create_task(task_path, track_task)
|
||||||
|
tasker.create_task(task_path, album_task)
|
||||||
|
tasker.create_task(task_path, artist_task)
|
@ -435,7 +435,7 @@ def create_tag(username: str, tag_id: str):
|
|||||||
logger.error(f'{tag_id} already exists for {username}')
|
logger.error(f'{tag_id} already exists for {username}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return parse_tag_reference(user.db_ref.collection(u'tags').add({
|
user.db_ref.collection(u'tags').add({
|
||||||
'tag_id': tag_id,
|
'tag_id': tag_id,
|
||||||
'name': tag_id,
|
'name': tag_id,
|
||||||
|
|
||||||
@ -447,4 +447,4 @@ def create_tag(username: str, tag_id: str):
|
|||||||
'proportion': 0.0,
|
'proportion': 0.0,
|
||||||
'total_user_scrobbles': 0,
|
'total_user_scrobbles': 0,
|
||||||
'last_updated': None
|
'last_updated': None
|
||||||
})[1])
|
})
|
||||||
|
@ -48,6 +48,7 @@ class Tag:
|
|||||||
|
|
||||||
'count': self.count,
|
'count': self.count,
|
||||||
'proportion': self.proportion,
|
'proportion': self.proportion,
|
||||||
|
'total_user_scrobbles': self.total_user_scrobbles,
|
||||||
|
|
||||||
'last_updated': self.last_updated
|
'last_updated': self.last_updated
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,21 @@ class BarChart extends Component {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
yAxes: [
|
yAxes: [{
|
||||||
{
|
|
||||||
ticks: {
|
ticks: {
|
||||||
min: 0
|
fontColor: "#d8d8d8",
|
||||||
|
fontSize: 16,
|
||||||
|
stepSize: 1,
|
||||||
|
beginAtZero: true
|
||||||
}
|
}
|
||||||
|
}],
|
||||||
|
xAxes: [{
|
||||||
|
ticks: {
|
||||||
|
fontColor: "#d8d8d8",
|
||||||
|
fontSize: 16,
|
||||||
|
stepSize: 1
|
||||||
}
|
}
|
||||||
]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,7 @@ import ListItem from '@material-ui/core/ListItem';
|
|||||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||||
import ListItemText from '@material-ui/core/ListItemText';
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
import HomeIcon from '@material-ui/icons/Home';
|
import HomeIcon from '@material-ui/icons/Home';
|
||||||
import { Build, PieChart, QueueMusic, ExitToApp, AccountCircle, KeyboardBackspace } from '@material-ui/icons'
|
import { Build, PieChart, QueueMusic, ExitToApp, AccountCircle, KeyboardBackspace, GroupWork } from '@material-ui/icons'
|
||||||
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
@ -32,6 +32,8 @@ const LazyPlaylists = React.lazy(() => import("./Playlist/AllPlaylistsRouter"))
|
|||||||
const LazyPlaylistView = React.lazy(() => import("./Playlist/View/PlaylistRouter"))
|
const LazyPlaylistView = React.lazy(() => import("./Playlist/View/PlaylistRouter"))
|
||||||
const LazySettings = React.lazy(() => import("./Settings/SettingsRouter"))
|
const LazySettings = React.lazy(() => import("./Settings/SettingsRouter"))
|
||||||
const LazyAdmin = React.lazy(() => import("./Admin/AdminRouter"))
|
const LazyAdmin = React.lazy(() => import("./Admin/AdminRouter"))
|
||||||
|
const LazyTags = React.lazy(() => import("./Tag/TagRouter"))
|
||||||
|
const LazyTag = React.lazy(() => import("./Tag/View"))
|
||||||
|
|
||||||
class MusicTools extends Component {
|
class MusicTools extends Component {
|
||||||
|
|
||||||
@ -117,6 +119,10 @@ class MusicTools extends Component {
|
|||||||
<ListItemIcon><QueueMusic /></ListItemIcon>
|
<ListItemIcon><QueueMusic /></ListItemIcon>
|
||||||
<ListItemText primary="Playlists" />
|
<ListItemText primary="Playlists" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem button key="tags" component={Link} to='/app/tags'>
|
||||||
|
<ListItemIcon><GroupWork /></ListItemIcon>
|
||||||
|
<ListItemText primary="Tags" />
|
||||||
|
</ListItem>
|
||||||
<ListItem button key="maths" component={Link} to='/app/maths/count'>
|
<ListItem button key="maths" component={Link} to='/app/maths/count'>
|
||||||
<ListItemIcon><PieChart /></ListItemIcon>
|
<ListItemIcon><PieChart /></ListItemIcon>
|
||||||
<ListItemText primary="Maths" />
|
<ListItemText primary="Maths" />
|
||||||
@ -147,6 +153,8 @@ class MusicTools extends Component {
|
|||||||
<React.Suspense fallback={<LoadingMessage/>}>
|
<React.Suspense fallback={<LoadingMessage/>}>
|
||||||
<Route path="/app" exact component={LazyIndex} />
|
<Route path="/app" exact component={LazyIndex} />
|
||||||
<Route path="/app/playlists" component={LazyPlaylists} />
|
<Route path="/app/playlists" component={LazyPlaylists} />
|
||||||
|
<Route path="/app/tags" component={LazyTags} />
|
||||||
|
<Route path="/app/tag/:tag_id" component={LazyTag} />
|
||||||
<Route path="/app/maths" component={LazyMaths} />
|
<Route path="/app/maths" component={LazyMaths} />
|
||||||
<Route path="/app/settings" component={LazySettings} />
|
<Route path="/app/settings" component={LazySettings} />
|
||||||
{ this.state.type == 'admin' && <Route path="/app/admin" component={LazyAdmin} /> }
|
{ this.state.type == 'admin' && <Route path="/app/admin" component={LazyAdmin} /> }
|
||||||
|
@ -97,18 +97,24 @@ export class Edit extends Component{
|
|||||||
axios.all([this.getPlaylistInfo(), this.getPlaylists()])
|
axios.all([this.getPlaylistInfo(), this.getPlaylists()])
|
||||||
.then(axios.spread((info, playlists) => {
|
.then(axios.spread((info, playlists) => {
|
||||||
|
|
||||||
info.data.parts.sort(function(a, b){
|
info.data.parts.sort((a, b) => {
|
||||||
if(a.toLowerCase() < b.toLowerCase()) { return -1; }
|
if(a.toLowerCase() < b.toLowerCase()) { return -1; }
|
||||||
if(a.toLowerCase() > b.toLowerCase()) { return 1; }
|
if(a.toLowerCase() > b.toLowerCase()) { return 1; }
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
info.data.playlist_references.sort(function(a, b){
|
info.data.playlist_references.sort((a, b) => {
|
||||||
if(a.toLowerCase() < b.toLowerCase()) { return -1; }
|
if(a.toLowerCase() < b.toLowerCase()) { return -1; }
|
||||||
if(a.toLowerCase() > b.toLowerCase()) { return 1; }
|
if(a.toLowerCase() > b.toLowerCase()) { return 1; }
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
playlists.data.playlists.sort( (a, b) => {
|
||||||
|
if(a.name.toLowerCase() < b.name.toLowerCase()) { return -1; }
|
||||||
|
if(a.name.toLowerCase() > b.name.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
var filteredPlaylists = playlists.data.playlists.filter((entry) => entry.name != this.state.name);
|
var filteredPlaylists = playlists.data.playlists.filter((entry) => entry.name != this.state.name);
|
||||||
|
|
||||||
this.setState(info.data);
|
this.setState(info.data);
|
||||||
@ -421,7 +427,7 @@ export class Edit extends Component{
|
|||||||
<Grid item xs={8} sm={8} md={3}>
|
<Grid item xs={8} sm={8} md={3}>
|
||||||
<TextField
|
<TextField
|
||||||
name="newPlaylistName"
|
name="newPlaylistName"
|
||||||
variant="outlined"
|
variant="filled"
|
||||||
label="Spotify Playlist"
|
label="Spotify Playlist"
|
||||||
value={this.state.newPlaylistName}
|
value={this.state.newPlaylistName}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
@ -476,6 +482,7 @@ export class Edit extends Component{
|
|||||||
<TextField type="number"
|
<TextField type="number"
|
||||||
name="recommendation_sample"
|
name="recommendation_sample"
|
||||||
label="Recommendation Size"
|
label="Recommendation Size"
|
||||||
|
variant="filled"
|
||||||
value={this.state.recommendation_sample}
|
value={this.state.recommendation_sample}
|
||||||
onChange={this.handleInputChange}></TextField>
|
onChange={this.handleInputChange}></TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -485,6 +492,7 @@ export class Edit extends Component{
|
|||||||
<TextField type="number"
|
<TextField type="number"
|
||||||
name="chart_limit"
|
name="chart_limit"
|
||||||
label="Chart Size"
|
label="Chart Size"
|
||||||
|
variant="filled"
|
||||||
value={this.state.chart_limit}
|
value={this.state.chart_limit}
|
||||||
onChange={this.handleInputChange}></TextField>
|
onChange={this.handleInputChange}></TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
85
src/js/Tag/New.js
Normal file
85
src/js/Tag/New.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
import { Card, Button, TextField, CardActions, CardContent, Typography, Grid } from '@material-ui/core';
|
||||||
|
|
||||||
|
import showMessage from "../Toast.js"
|
||||||
|
|
||||||
|
class NewTag extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
tag_id: ''
|
||||||
|
}
|
||||||
|
this.handleInputChange = this.handleInputChange.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange(event){
|
||||||
|
this.setState({
|
||||||
|
tag_id: event.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(event){
|
||||||
|
var tag_id = this.state.tag_id;
|
||||||
|
this.setState({
|
||||||
|
tag_id: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
if(tag_id.length != 0){
|
||||||
|
axios.get('/api/tag')
|
||||||
|
.then((response) => {
|
||||||
|
var tag_ids = response.data.tags.map(entry => entry.tag_id)
|
||||||
|
|
||||||
|
var sameTag_id = tag_ids.includes(this.state.tag_id);
|
||||||
|
if(sameTag_id == false){
|
||||||
|
axios.post(`/api/tag/${tag_id}`).then((response) => {
|
||||||
|
showMessage(`${tag_id} Created`);
|
||||||
|
}).catch((error) => {
|
||||||
|
showMessage(`Error Creating Tag (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage('Named Tag Already Exists');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showMessage(`Error Getting Tags (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage('Enter Name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div style={{maxWidth: '500px', margin: 'auto', marginTop: '20px'}}>
|
||||||
|
<Card align="center">
|
||||||
|
<CardContent>
|
||||||
|
<Grid container spacing={5}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h3">New Tag</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
label="Name"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
name="tag_id"
|
||||||
|
value={this.state.tag_id}
|
||||||
|
className="full-width" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions>
|
||||||
|
<Button variant="contained" color="primary" className="full-width" onClick={this.handleSubmit}>Create</Button>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewTag;
|
@ -15,14 +15,12 @@ class TagList extends Component {
|
|||||||
isLoading: true
|
isLoading: true
|
||||||
}
|
}
|
||||||
this.getTags();
|
this.getTags();
|
||||||
this.handleRunTag = this.handleRunTag.bind(this);
|
|
||||||
this.handleDeleteTag = this.handleDeleteTag.bind(this);
|
this.handleDeleteTag = this.handleDeleteTag.bind(this);
|
||||||
this.handleRunAll = this.handleRunAll.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTags(){
|
getTags(){
|
||||||
var self = this;
|
var self = this;
|
||||||
axios.get('/api/tags')
|
axios.get('/api/tag')
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
|
||||||
var tags = response.data.tags.slice();
|
var tags = response.data.tags.slice();
|
||||||
@ -34,69 +32,29 @@ class TagList extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.setState({
|
self.setState({
|
||||||
playlists: tags,
|
tags: tags,
|
||||||
isLoading: false
|
isLoading: false
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
showMessage(`Error Getting Playlists (${error.response.status})`);
|
showMessage(`Error Getting Tags (${error.response.status})`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRunTag(name, event){
|
handleDeleteTag(tag_id, event){
|
||||||
axios.get('/api/user')
|
axios.delete(`/api/tag/${tag_id}`)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if(response.data.spotify_linked == true){
|
showMessage(`${tag_id} Deleted`);
|
||||||
axios.get('/api/tag/run', {params: {name: name}})
|
|
||||||
.then((response) => {
|
|
||||||
showMessage(`${name} ran`);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
showMessage(`Error Running ${name} (${error.response.status})`);
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
showMessage(`Link Spotify Before Running`);
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
showMessage(`Error Running ${this.state.name} (${error.response.status})`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteTag(name, event){
|
|
||||||
axios.delete('/api/playlist', { params: { name: name } })
|
|
||||||
.then((response) => {
|
|
||||||
showMessage(`${name} Deleted`);
|
|
||||||
this.getTags();
|
this.getTags();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
showMessage(`Error Deleting ${name} (${error.response.status})`);
|
showMessage(`Error Deleting ${tag_id} (${error.response.status})`);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRunAll(event){
|
|
||||||
axios.get('/api/user')
|
|
||||||
.then((response) => {
|
|
||||||
if(response.data.spotify_linked == true){
|
|
||||||
axios.get('/api/tag/run/user')
|
|
||||||
.then((response) => {
|
|
||||||
showMessage("All Tags Ran");
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
showMessage(`Error Running All (${error.response.status})`);
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
showMessage(`Link Spotify Before Running`);
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
showMessage(`Error Running ${this.state.name} (${error.response.status})`);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const grid = <TagGrid tags={this.state.tags}
|
const grid = <TagGrid tags={this.state.tags}
|
||||||
handleRunTag={this.handleRunTag}
|
handleDeleteTag={this.handleDeleteTag}/>;
|
||||||
handleDeleteTag={this.handleDeleteTag}
|
|
||||||
handleRunAll={this.handleRunAll}/>;
|
|
||||||
|
|
||||||
return this.state.isLoading ? <CircularProgress /> : grid;
|
return this.state.isLoading ? <CircularProgress /> : grid;
|
||||||
}
|
}
|
||||||
@ -116,7 +74,6 @@ function TagGrid(props){
|
|||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
className="full-width">
|
className="full-width">
|
||||||
<Button component={Link} to='tags/new' >New</Button>
|
<Button component={Link} to='tags/new' >New</Button>
|
||||||
<Button onClick={props.handleRunAll}>Run All</Button>
|
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Grid>
|
</Grid>
|
||||||
{ props.tags.length == 0 ? (
|
{ props.tags.length == 0 ? (
|
||||||
@ -146,9 +103,8 @@ function TagCard(props){
|
|||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained">
|
variant="contained">
|
||||||
<Button component={Link} to={getTagLink(props.tag.name)}>View</Button>
|
<Button component={Link} to={getTagLink(props.tag.tag_id)}>View</Button>
|
||||||
<Button onClick={(e) => props.handleRunTag(props.tag.name, e)}>Update</Button>
|
<Button onClick={(e) => props.handleDeleteTag(props.tag.tag_id, e)}>Delete</Button>
|
||||||
<Button onClick={(e) => props.handleDeleteTag(props.tag.name, e)}>Delete</Button>
|
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
@ -157,7 +113,7 @@ function TagCard(props){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTagLink(tagName){
|
function getTagLink(tagName){
|
||||||
return `/app/tag/${tagName}/edit`;
|
return `/app/tag/${tagName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TagList;
|
export default TagList;
|
20
src/js/Tag/TagRouter.js
Normal file
20
src/js/Tag/TagRouter.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import { Route, Switch } from "react-router-dom";
|
||||||
|
|
||||||
|
import TagList from "./TagList.js"
|
||||||
|
import New from "./New.js"
|
||||||
|
|
||||||
|
class TagRouter extends Component {
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Switch>
|
||||||
|
<Route exact path={`${this.props.match.url}/`} component={TagList} />
|
||||||
|
<Route path={`${this.props.match.url}/new`} component={New} />
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TagRouter;
|
389
src/js/Tag/View.js
Normal file
389
src/js/Tag/View.js
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
import { Card, Button, CircularProgress, CardActions, CardContent, FormControl, InputLabel, Select, Typography, Grid, TextField } from '@material-ui/core';
|
||||||
|
import { Delete } from '@material-ui/icons';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
import showMessage from "../Toast.js";
|
||||||
|
import BarChart from "../Maths/BarChart.js";
|
||||||
|
import PieChart from "../Maths/PieChart.js";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
root: {
|
||||||
|
background: '#9e9e9e',
|
||||||
|
color: '#212121',
|
||||||
|
align: "center"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
class View extends Component{
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
tag_id: props.match.params.tag_id,
|
||||||
|
tag: {
|
||||||
|
name: "",
|
||||||
|
tracks: [],
|
||||||
|
albums: [],
|
||||||
|
artists: []
|
||||||
|
},
|
||||||
|
|
||||||
|
addType: 'artists',
|
||||||
|
|
||||||
|
name: '',
|
||||||
|
artist: '',
|
||||||
|
|
||||||
|
isLoading: true
|
||||||
|
}
|
||||||
|
this.handleInputChange = this.handleInputChange.bind(this);
|
||||||
|
this.handleRun = this.handleRun.bind(this);
|
||||||
|
this.handleRemoveObj = this.handleRemoveObj.bind(this);
|
||||||
|
|
||||||
|
this.handleAdd = this.handleAdd.bind(this);
|
||||||
|
this.handleChangeAddType = this.handleChangeAddType.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
this.getTag();
|
||||||
|
var intervalId = setInterval(() => {this.getTag(false)}, 5000);
|
||||||
|
var timeoutId = setTimeout(() => {clearInterval(this.state.intervalId)}, 300000);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
intervalId: intervalId,
|
||||||
|
timeoutId: timeoutId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(){
|
||||||
|
clearInterval(this.state.intervalId);
|
||||||
|
clearTimeout(this.state.timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTag(error_toast = true){
|
||||||
|
axios.get(`/api/tag/${ this.state.tag_id }`)
|
||||||
|
.then( (response) => {
|
||||||
|
|
||||||
|
var tag = response.data.tag;
|
||||||
|
|
||||||
|
tag.artists = tag.artists.sort((a, b) => {
|
||||||
|
if(a.name.toLowerCase() < b.name.toLowerCase()) { return -1; }
|
||||||
|
if(a.name.toLowerCase() > b.name.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
tag.albums = tag.albums.sort((a, b) => {
|
||||||
|
if(a.artist.toLowerCase() < b.artist.toLowerCase()) { return -1; }
|
||||||
|
if(a.artist.toLowerCase() > b.artist.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
tag.tracks = tag.tracks.sort((a, b) => {
|
||||||
|
if(a.artist.toLowerCase() < b.artist.toLowerCase()) { return -1; }
|
||||||
|
if(a.artist.toLowerCase() > b.artist.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
tag: response.data.tag,
|
||||||
|
isLoading: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch( (error) => {
|
||||||
|
if(error_toast) {
|
||||||
|
showMessage(`Error Getting Tag Info (${error.response.status})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange(event){
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
[event.target.name]: event.target.value
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRun(event){
|
||||||
|
axios.get('/api/user')
|
||||||
|
.then((response) => {
|
||||||
|
if(response.data.lastfm_username != null){
|
||||||
|
axios.get(`/api/tag/${this.state.tag_id}/update`)
|
||||||
|
.then((reponse) => {
|
||||||
|
showMessage(`${this.state.name} Updating`);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showMessage(`Error Updating ${this.state.tag_id} (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
showMessage(`Add a Last.fm Username In Settings`);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
showMessage(`Error Updating ${this.state.tag_id} (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRemoveObj(music_obj, addType, event){
|
||||||
|
var startingItems = this.state.tag[addType].slice();
|
||||||
|
|
||||||
|
var items = this.state.tag[addType].slice();
|
||||||
|
items = items.filter((item) => {
|
||||||
|
if(addType == 'albums' || addType == 'tracks') {
|
||||||
|
return item.name.toLowerCase() != music_obj.name.toLowerCase() || item.artist.toLowerCase() != music_obj.artist.toLowerCase();
|
||||||
|
}else{
|
||||||
|
return item.name.toLowerCase() != music_obj.name.toLowerCase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var tag = this.state.tag;
|
||||||
|
tag[addType] = items;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
tag: tag
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.put(`/api/tag/${this.state.tag_id}`, {
|
||||||
|
[addType]: items
|
||||||
|
})
|
||||||
|
.catch( (error) => {
|
||||||
|
showMessage(`Error Removing ${music_obj.name} (${error.response.status})`);
|
||||||
|
|
||||||
|
redo_tag[addType] = startingItems;
|
||||||
|
this.setState({
|
||||||
|
tag: redo_tag
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeAddType(type){
|
||||||
|
this.setState({
|
||||||
|
addType: type
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAdd(){
|
||||||
|
|
||||||
|
var addType = this.state.addType;
|
||||||
|
var music_obj = {
|
||||||
|
name: this.state.name,
|
||||||
|
artist: this.state.artist
|
||||||
|
}
|
||||||
|
|
||||||
|
if(music_obj.name == ''){
|
||||||
|
showMessage(`Enter Name`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(music_obj.artist == '' && addType != 'artists'){
|
||||||
|
showMessage(`Enter Artist`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = this.state.tag[addType].slice().filter((item) => {
|
||||||
|
if(addType != 'artists') {
|
||||||
|
return item.name.toLowerCase() == music_obj.name.toLowerCase() && item.artist.toLowerCase() == music_obj.artist.toLowerCase();
|
||||||
|
}else{
|
||||||
|
return item.name.toLowerCase() == music_obj.name.toLowerCase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(list.length != 0) {
|
||||||
|
showMessage(`${music_obj.name} already present`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list = this.state.tag[addType].slice();
|
||||||
|
if(addType == 'artist'){
|
||||||
|
list.push({
|
||||||
|
name: music_obj.name
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
list.push(music_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addType == "artists"){
|
||||||
|
list = list.sort((a, b) => {
|
||||||
|
if(a.name.toLowerCase() < b.name.toLowerCase()) { return -1; }
|
||||||
|
if(a.name.toLowerCase() > b.name.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
list = list.sort((a, b) => {
|
||||||
|
if(a.artist.toLowerCase() < b.artist.toLowerCase()) { return -1; }
|
||||||
|
if(a.artist.toLowerCase() > b.artist.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var tag = this.state.tag;
|
||||||
|
tag[addType] = list;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
tag: tag,
|
||||||
|
name: '',
|
||||||
|
artist: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.put(`/api/tag/${this.state.tag_id}`, {
|
||||||
|
[addType]: list
|
||||||
|
})
|
||||||
|
.catch( (error) => {
|
||||||
|
showMessage(`Error Adding ${music_obj.name} (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
|
||||||
|
var all = [...this.state.tag.artists, ...this.state.tag.albums, ...this.state.tag.tracks];
|
||||||
|
|
||||||
|
var data = all.map((entry) => {
|
||||||
|
return {
|
||||||
|
"label": entry.name,
|
||||||
|
"value": entry.count
|
||||||
|
};
|
||||||
|
}).sort((a, b) => {
|
||||||
|
if(a.value < b.value) { return 1; }
|
||||||
|
if(a.value > b.value) { return -1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const table = (
|
||||||
|
<div style={{maxWidth: '1000px', margin: 'auto', marginTop: '20px'}}>
|
||||||
|
<Card align="center">
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h2" color="textPrimary">{this.state.tag.name}</Typography>
|
||||||
|
<Grid container spacing={5}>
|
||||||
|
|
||||||
|
{ this.state.tag.artists.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Artists</Typography></Grid> }
|
||||||
|
{ this.state.tag.artists.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.artists} addType="artists"/> }
|
||||||
|
|
||||||
|
{ this.state.tag.albums.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Albums</Typography></Grid> }
|
||||||
|
{ this.state.tag.albums.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.albums} addType="albums"/> }
|
||||||
|
|
||||||
|
{ this.state.tag.tracks.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Tracks</Typography></Grid> }
|
||||||
|
{ this.state.tag.tracks.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.tracks} addType="tracks"/> }
|
||||||
|
<Grid item xs={12} sm={this.state.addType != 'artists' ? 3 : 4} md={this.state.addType != 'artists' ? 3 : 4}>
|
||||||
|
<TextField
|
||||||
|
name="name"
|
||||||
|
label="Name"
|
||||||
|
variant="filled"
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={this.handleInputChange}></TextField>
|
||||||
|
</Grid>
|
||||||
|
{ this.state.addType != 'artists' &&
|
||||||
|
<Grid item xs={12} sm={3} md={4}>
|
||||||
|
<TextField
|
||||||
|
name="artist"
|
||||||
|
label="Artist"
|
||||||
|
variant="filled"
|
||||||
|
value={this.state.artist}
|
||||||
|
onChange={this.handleInputChange}></TextField>
|
||||||
|
</Grid>
|
||||||
|
}
|
||||||
|
<Grid item xs={12} sm={this.state.addType != 'artists' ? 2 : 4} md={this.state.addType != 'artists' ? 2 : 4}>
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel htmlFor="addType">Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
native
|
||||||
|
value={this.state.addType}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
inputProps={{
|
||||||
|
name: "addType",
|
||||||
|
id: "addType",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="artists">Artist</option>
|
||||||
|
<option value="albums">Album</option>
|
||||||
|
<option value="tracks">Track</option>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={this.state.addType != 'artists' ? 3 : 4} md={this.state.addType != 'artists' ? 3 : 4}>
|
||||||
|
<Button variant="contained" onClick={this.handleAdd} className="full-width">Add</Button>
|
||||||
|
</Grid>
|
||||||
|
<StatsCard count={this.state.tag.count} proportion={this.state.tag.proportion}></StatsCard>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<PieChart data={data}/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<BarChart data={data} title='scrobbles'/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions>
|
||||||
|
<Button onClick={this.handleRun} variant="contained" color="primary" className="full-width" >Update</Button>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.state.isLoading ? <CircularProgress /> : table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default View;
|
||||||
|
|
||||||
|
function ListBlock(props) {
|
||||||
|
return <Grid container
|
||||||
|
spacing={3}
|
||||||
|
direction="row"
|
||||||
|
justify="flex-start"
|
||||||
|
alignItems="flex-start"
|
||||||
|
style={{padding: '24px'}}>
|
||||||
|
{props.list.map((music_obj) => <BlockGridItem music_obj={ music_obj } key={ music_obj.name }
|
||||||
|
handler={ props.handler } addType={ props.addType }/>)}
|
||||||
|
</Grid>
|
||||||
|
}
|
||||||
|
|
||||||
|
function BlockGridItem (props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<Grid item xs={12} sm={4} md={3}>
|
||||||
|
<Card variant="outlined" className={classes.root}>
|
||||||
|
<CardContent>
|
||||||
|
<Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h4" color="textSecondary" className={classes.root}>{ props.music_obj.name }</Typography>
|
||||||
|
</Grid>
|
||||||
|
{ 'artist' in props.music_obj &&
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="body1" color="textSecondary" className={classes.root}>{ props.music_obj.artist }</Typography>
|
||||||
|
</Grid>
|
||||||
|
}
|
||||||
|
{ 'count' in props.music_obj &&
|
||||||
|
<Grid item xs={8}>
|
||||||
|
<Typography variant="h4" color="textPrimary" className={classes.root}>{ props.music_obj.count }</Typography>
|
||||||
|
</Grid>
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions>
|
||||||
|
<Button className="full-width" color="secondary" variant="contained" aria-label="delete" onClick={(e) => props.handler(props.music_obj, props.addType, e)} startIcon={<Delete />}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatsCard (props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Card variant="outlined" className={classes.root}>
|
||||||
|
<CardContent>
|
||||||
|
<Grid container spacing={10}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h1" color="textPrimary" className={classes.root}>= { props.count }</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h4" color="textSecondary" className={classes.root}>{ Math.round(props.proportion) }%</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user