added playlist counting
This commit is contained in:
parent
849c0a5fa6
commit
b54ef10541
@ -91,7 +91,7 @@ def lastfm_username_required(func):
|
|||||||
user_dict = database.get_user_doc_ref(kwargs.get('username')).get().to_dict()
|
user_dict = database.get_user_doc_ref(kwargs.get('username')).get().to_dict()
|
||||||
|
|
||||||
if user_dict:
|
if user_dict:
|
||||||
if user_dict.get('lastfm_username'):
|
if user_dict.get('lastfm_username') and len(user_dict.get('lastfm_username')) > 0:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
logger.warning(f'no last.fm username for {user_dict["username"]}')
|
logger.warning(f'no last.fm username for {user_dict["username"]}')
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
from flask import Blueprint, jsonify, request
|
from flask import Blueprint, jsonify, request
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
from music.api.decorators import login_or_basic_auth, lastfm_username_required, spotify_link_required
|
from music.api.decorators import login_or_basic_auth, lastfm_username_required, spotify_link_required, cloud_task
|
||||||
import music.db.database as database
|
import music.db.database as database
|
||||||
|
from music.tasks.refresh_lastfm_stats import refresh_lastfm_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 tasks_v2
|
||||||
|
|
||||||
blueprint = Blueprint('spotfm-api', __name__)
|
blueprint = Blueprint('spotfm-api', __name__)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
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
|
||||||
@ -55,3 +63,56 @@ def count(username=None):
|
|||||||
}), 200
|
}), 200
|
||||||
else:
|
else:
|
||||||
return jsonify({'error': f'playlist {playlist_name} not found'}), 404
|
return jsonify({'error': f'playlist {playlist_name} not found'}), 404
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/playlist/refresh', methods=['GET'])
|
||||||
|
@login_or_basic_auth
|
||||||
|
@spotify_link_required
|
||||||
|
@lastfm_username_required
|
||||||
|
def playlist_refresh(username=None):
|
||||||
|
|
||||||
|
playlist_name = request.args.get('name', None)
|
||||||
|
|
||||||
|
if playlist_name:
|
||||||
|
|
||||||
|
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||||
|
create_refresh_playlist_task(username, playlist_name)
|
||||||
|
else:
|
||||||
|
refresh_lastfm_stats(username, playlist_name)
|
||||||
|
|
||||||
|
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning('no playlist requested')
|
||||||
|
return jsonify({"error": 'no name requested'}), 400
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/playlist/refresh/task', methods=['POST'])
|
||||||
|
@cloud_task
|
||||||
|
def run_playlist_task():
|
||||||
|
|
||||||
|
payload = request.get_data(as_text=True)
|
||||||
|
if payload:
|
||||||
|
payload = json.loads(payload)
|
||||||
|
|
||||||
|
logger.info(f'running {payload["username"]} / {payload["name"]}')
|
||||||
|
|
||||||
|
refresh_lastfm_stats(payload['username'], payload['name'])
|
||||||
|
|
||||||
|
return jsonify({'message': 'executed playlist', 'status': 'success'}), 200
|
||||||
|
|
||||||
|
|
||||||
|
def create_refresh_playlist_task(username, playlist_name):
|
||||||
|
|
||||||
|
task = {
|
||||||
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
|
'http_method': 'POST',
|
||||||
|
'relative_uri': '/api/spotfm/playlist/refresh/task',
|
||||||
|
'body': json.dumps({
|
||||||
|
'username': username,
|
||||||
|
'name': playlist_name
|
||||||
|
}).encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasker.create_task(task_path, task)
|
||||||
|
@ -123,7 +123,7 @@ def get_user_playlists_collection(user_id: str) -> firestore.CollectionReference
|
|||||||
return playlists
|
return playlists
|
||||||
|
|
||||||
|
|
||||||
def get_user_playlist_ref_by_username(user: str, playlist: str) -> Optional[firestore.CollectionReference]:
|
def get_user_playlist_ref_by_username(user: str, playlist: str) -> Optional[firestore.DocumentReference]:
|
||||||
|
|
||||||
user_ref = get_user_doc_ref(user)
|
user_ref = get_user_doc_ref(user)
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ def get_user_playlist_ref_by_username(user: str, playlist: str) -> Optional[fire
|
|||||||
|
|
||||||
|
|
||||||
def get_user_playlist_ref_by_user_ref(user_ref: firestore.DocumentReference,
|
def get_user_playlist_ref_by_user_ref(user_ref: firestore.DocumentReference,
|
||||||
playlist: str) -> Optional[firestore.CollectionReference]:
|
playlist: str) -> Optional[firestore.DocumentReference]:
|
||||||
|
|
||||||
playlist_collection = get_user_playlists_collection(user_ref.id)
|
playlist_collection = get_user_playlists_collection(user_ref.id)
|
||||||
|
|
||||||
|
36
music/tasks/refresh_lastfm_stats.py
Normal file
36
music/tasks/refresh_lastfm_stats.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from google.cloud import firestore
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import music.db.database as database
|
||||||
|
|
||||||
|
from spotfm.maths.counter import Counter
|
||||||
|
from spotframework.model.uri import Uri
|
||||||
|
|
||||||
|
db = firestore.Client()
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_lastfm_stats(username, playlist_name):
|
||||||
|
|
||||||
|
logger.info(f'refreshing {playlist_name} stats for {username}')
|
||||||
|
|
||||||
|
fmnet = database.get_authed_lastfm_network(username=username)
|
||||||
|
spotnet = database.get_authed_spotify_network(username=username)
|
||||||
|
counter = Counter(fmnet=fmnet, spotnet=spotnet)
|
||||||
|
|
||||||
|
database_ref = database.get_user_playlist_ref_by_username(user=username, playlist=playlist_name)
|
||||||
|
|
||||||
|
playlist_dict = database_ref.get().to_dict()
|
||||||
|
count = counter.count(Uri(playlist_dict['uri']))
|
||||||
|
|
||||||
|
user_count = fmnet.get_user_scrobble_count()
|
||||||
|
percent = round((count * 100) / user_count, 2)
|
||||||
|
|
||||||
|
database_ref.update({
|
||||||
|
'lastfm_stat_count': count,
|
||||||
|
'lastfm_stat_percent': percent,
|
||||||
|
'lastfm_stat_last_refresh': datetime.utcnow()
|
||||||
|
})
|
@ -146,7 +146,7 @@ function PlaylistLink(props){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPlaylistLink(playlistName){
|
function getPlaylistLink(playlistName){
|
||||||
return '/app/playlist/' + playlistName;
|
return `/app/playlist/${playlistName}/edit`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PlaylistsView;
|
export default PlaylistsView;
|
79
src/js/Playlist/View/Count.js
Normal file
79
src/js/Playlist/View/Count.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
import showMessage from "../../Toast.js"
|
||||||
|
|
||||||
|
class Count extends Component {
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
name: props.name,
|
||||||
|
lastfm_refresh: 'never',
|
||||||
|
lastfm_percent: 0,
|
||||||
|
count: 0,
|
||||||
|
isLoading: true
|
||||||
|
}
|
||||||
|
this.getUserInfo();
|
||||||
|
|
||||||
|
this.updateStats = this.updateStats.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserInfo(){
|
||||||
|
axios.get(`/api/playlist?name=${ this.state.name }`)
|
||||||
|
.then((response) => {
|
||||||
|
if(response.data.lastfm_stat_last_refresh != undefined){
|
||||||
|
this.setState({
|
||||||
|
count: response.data.lastfm_stat_count,
|
||||||
|
lastfm_refresh: response.data.lastfm_stat_last_refresh,
|
||||||
|
lastfm_percent: response.data.lastfm_stat_percent,
|
||||||
|
isLoading: false
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
showMessage('no stats for this playlist');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showMessage(`error getting playlist info (${error.response.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStats(){
|
||||||
|
axios.get(`/api/spotfm/playlist/refresh?name=${ this.state.name }`)
|
||||||
|
.then((response) => {
|
||||||
|
showMessage('stats refresh queued');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if(error.response.status == 401){
|
||||||
|
showMessage('missing either spotify or last.fm link');
|
||||||
|
}else{
|
||||||
|
showMessage(`error refreshing (${error.response.status})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="ui-text center-text text-no-select">scrobble count: <b>{this.state.count.toLocaleString()}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="ui-text center-text text-no-select">that's <b>{this.state.lastfm_percent}%</b> of all scrobbles</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="ui-text center-text text-no-select">last updated <b>{this.state.lastfm_refresh.toLocaleString()}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2">
|
||||||
|
<button style={{width: "100%"}} className="button" onClick={this.updateStats}>update</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default Count;
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
import showMessage from "../Toast.js"
|
import showMessage from "../../Toast.js"
|
||||||
|
|
||||||
var thisMonth = [
|
var thisMonth = [
|
||||||
'january',
|
'january',
|
||||||
@ -33,12 +33,12 @@ var lastMonth = [
|
|||||||
'november'
|
'november'
|
||||||
];
|
];
|
||||||
|
|
||||||
class PlaylistView extends Component{
|
class Edit extends Component{
|
||||||
|
|
||||||
constructor(props){
|
constructor(props){
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
name: this.props.match.params.name,
|
name: this.props.name,
|
||||||
parts: [],
|
parts: [],
|
||||||
playlists: [],
|
playlists: [],
|
||||||
filteredPlaylists: [],
|
filteredPlaylists: [],
|
||||||
@ -352,162 +352,157 @@ class PlaylistView extends Component{
|
|||||||
var date = new Date();
|
var date = new Date();
|
||||||
|
|
||||||
const table = (
|
const table = (
|
||||||
<table className="app-table max-width">
|
<tbody>
|
||||||
<thead>
|
{ this.state.playlist_references.length > 0 && <tr><td colSpan="2" className="ui-text center-text text-no-select" style={{fontStyle: 'italic'}}>managed</td></tr> }
|
||||||
<tr>
|
{ this.state.playlist_references.length > 0 && <ListBlock handler={this.handleRemoveReference} list={this.state.playlist_references}/> }
|
||||||
<th colSpan="2"><h1 className="text-no-select">{ this.state.name }</h1></th>
|
|
||||||
</tr>
|
{ this.state.parts.length > 0 && <tr><td colSpan="2" className="ui-text center-text text-no-select" style={{fontStyle: 'italic'}}>spotify</td></tr> }
|
||||||
</thead>
|
{ this.state.parts.length > 0 && <ListBlock handler={this.handleRemovePart} list={this.state.parts}/> }
|
||||||
{ this.state.playlist_references.length > 0 && <ListBlock name="managed" handler={this.handleRemoveReference} list={this.state.playlist_references}/> }
|
<tr>
|
||||||
{ this.state.parts.length > 0 && <ListBlock name="spotify" handler={this.handleRemovePart} list={this.state.parts}/> }
|
<td colSpan="2" className="center-text ui-text text-no-select" style={{fontStyle: "italic"}}>
|
||||||
<tbody>
|
<br></br>spotify playlist can be the name of either your own created playlist or one you follow, names are case sensitive
|
||||||
<tr>
|
</td>
|
||||||
<td colSpan="2" className="center-text ui-text text-no-select" style={{fontStyle: "italic"}}>
|
</tr>
|
||||||
<br></br>spotify playlist can be the name of either your own created playlist or one you follow, names are case sensitive
|
<tr>
|
||||||
</td>
|
<td>
|
||||||
</tr>
|
<input type="text"
|
||||||
<tr>
|
name="newPlaylistName"
|
||||||
<td>
|
className="full-width"
|
||||||
<input type="text"
|
value={this.state.newPlaylistName}
|
||||||
name="newPlaylistName"
|
onChange={this.handleInputChange}
|
||||||
className="full-width"
|
placeholder="spotify playlist name"></input>
|
||||||
value={this.state.newPlaylistName}
|
</td>
|
||||||
onChange={this.handleInputChange}
|
<td>
|
||||||
placeholder="spotify playlist name"></input>
|
<button className="button full-width" onClick={this.handleAddPart}>add</button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
</tr>
|
||||||
<button className="button full-width" onClick={this.handleAddPart}>add</button>
|
<tr>
|
||||||
</td>
|
<td>
|
||||||
</tr>
|
<select name="newReferenceName"
|
||||||
<tr>
|
className="full-width"
|
||||||
<td>
|
value={this.state.newReferenceName}
|
||||||
<select name="newReferenceName"
|
onChange={this.handleInputChange}>
|
||||||
className="full-width"
|
{ this.state.playlists
|
||||||
value={this.state.newReferenceName}
|
.filter((entry) => entry.name != this.state.name)
|
||||||
onChange={this.handleInputChange}>
|
.map((entry) => <ReferenceEntry name={entry.name} key={entry.name} />) }
|
||||||
{ this.state.playlists
|
</select>
|
||||||
.filter((entry) => entry.name != this.state.name)
|
</td>
|
||||||
.map((entry) => <ReferenceEntry name={entry.name} key={entry.name} />) }
|
<td>
|
||||||
</select>
|
<button className="button full-width" onClick={this.handleAddReference}>add</button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
</tr>
|
||||||
<button className="button full-width" onClick={this.handleAddReference}>add</button>
|
<tr>
|
||||||
</td>
|
<td className="center-text ui-text text-no-select">
|
||||||
</tr>
|
shuffle output?
|
||||||
<tr>
|
</td>
|
||||||
<td className="center-text ui-text text-no-select">
|
<td>
|
||||||
shuffle output?
|
<input type="checkbox"
|
||||||
</td>
|
checked={this.state.shuffle}
|
||||||
<td>
|
onChange={this.handleShuffleChange}></input>
|
||||||
<input type="checkbox"
|
</td>
|
||||||
checked={this.state.shuffle}
|
</tr>
|
||||||
onChange={this.handleShuffleChange}></input>
|
<tr>
|
||||||
</td>
|
<td className="center-text ui-text text-no-select">
|
||||||
</tr>
|
include recommendations?
|
||||||
<tr>
|
</td>
|
||||||
<td className="center-text ui-text text-no-select">
|
<td>
|
||||||
include recommendations?
|
<input type="checkbox"
|
||||||
</td>
|
checked={this.state.include_recommendations}
|
||||||
<td>
|
onChange={this.handleIncludeRecommendationsChange}></input>
|
||||||
<input type="checkbox"
|
</td>
|
||||||
checked={this.state.include_recommendations}
|
</tr>
|
||||||
onChange={this.handleIncludeRecommendationsChange}></input>
|
<tr>
|
||||||
</td>
|
<td className="center-text ui-text text-no-select">
|
||||||
</tr>
|
number of recommendations
|
||||||
<tr>
|
</td>
|
||||||
<td className="center-text ui-text text-no-select">
|
<td>
|
||||||
number of recommendations
|
<input type="number"
|
||||||
</td>
|
name="recommendation_sample"
|
||||||
<td>
|
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"
|
<input type="number"
|
||||||
name="recommendation_sample"
|
name="day_boundary"
|
||||||
className="full-width"
|
className="full-width"
|
||||||
value={this.state.recommendation_sample}
|
value={this.state.day_boundary}
|
||||||
onChange={this.handleInputChange}></input>
|
onChange={this.handleInputChange}></input>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{ this.state.type == 'recents' &&
|
}
|
||||||
<tr>
|
{ this.state.type == 'recents' &&
|
||||||
<td className="center-text ui-text text-no-select">
|
<tr>
|
||||||
added since (days)
|
<td className="center-text ui-text text-no-select">
|
||||||
</td>
|
include {thisMonth[date.getMonth()]} playlist
|
||||||
<td>
|
</td>
|
||||||
<input type="number"
|
<td>
|
||||||
name="day_boundary"
|
<input type="checkbox"
|
||||||
className="full-width"
|
checked={this.state.add_this_month}
|
||||||
value={this.state.day_boundary}
|
onChange={this.handleThisMonthChange}></input>
|
||||||
onChange={this.handleInputChange}></input>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
}
|
||||||
}
|
{ this.state.type == 'recents' &&
|
||||||
{ this.state.type == 'recents' &&
|
<tr>
|
||||||
<tr>
|
<td className="center-text ui-text text-no-select">
|
||||||
<td className="center-text ui-text text-no-select">
|
include {lastMonth[date.getMonth()]} playlist
|
||||||
include {thisMonth[date.getMonth()]} playlist
|
</td>
|
||||||
</td>
|
<td>
|
||||||
<td>
|
<input type="checkbox"
|
||||||
<input type="checkbox"
|
checked={this.state.add_last_month}
|
||||||
checked={this.state.add_this_month}
|
onChange={this.handleLastMonthChange}></input>
|
||||||
onChange={this.handleThisMonthChange}></input>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
}
|
||||||
}
|
<tr>
|
||||||
{ this.state.type == 'recents' &&
|
<td className="center-text ui-text text-no-select">
|
||||||
<tr>
|
playlist type
|
||||||
<td className="center-text ui-text text-no-select">
|
</td>
|
||||||
include {lastMonth[date.getMonth()]} playlist
|
<td>
|
||||||
</td>
|
<select className="full-width"
|
||||||
<td>
|
name="type"
|
||||||
<input type="checkbox"
|
onChange={this.handleInputChange}
|
||||||
checked={this.state.add_last_month}
|
value={this.state.type}>
|
||||||
onChange={this.handleLastMonthChange}></input>
|
<option value="default">default</option>
|
||||||
</td>
|
<option value="recents">recents</option>
|
||||||
</tr>
|
</select>
|
||||||
}
|
</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td className="center-text ui-text text-no-select">
|
<tr>
|
||||||
playlist type
|
<td colSpan="2">
|
||||||
</td>
|
<button className="button full-width" onClick={this.handleRun}>run</button>
|
||||||
<td>
|
</td>
|
||||||
<select className="full-width"
|
</tr>
|
||||||
name="type"
|
</tbody>
|
||||||
onChange={this.handleInputChange}
|
|
||||||
value={this.state.type}>
|
|
||||||
<option value="default">default</option>
|
|
||||||
<option value="recents">recents</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colSpan="2">
|
|
||||||
<button className="button full-width" onClick={this.handleRun}>run</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = <p style={{textAlign: "center"}}>{ this.state.error_text }</p>;
|
const loadingMessage =
|
||||||
const loadingMessage = <p className="center-text">loading...</p>;
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p className="center-text">loading...</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>;
|
||||||
|
|
||||||
return this.state.isLoading ? loadingMessage : ( this.state.error ? error : table );
|
return this.state.isLoading ? loadingMessage : table;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReferenceEntry(props) {
|
function ReferenceEntry(props) {
|
||||||
return (
|
return <option value={props.name}>{props.name}</option>;
|
||||||
<option value={props.name}>{props.name}</option>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListBlock(props) {
|
function ListBlock(props) {
|
||||||
return (
|
return props.list.map((part) => <Row part={ part } key={ part } handler={props.handler}/>);
|
||||||
<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) {
|
function Row (props) {
|
||||||
@ -519,4 +514,4 @@ function Row (props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PlaylistView;
|
export default Edit;
|
36
src/js/Playlist/View/View.js
Normal file
36
src/js/Playlist/View/View.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import { BrowserRouter as Router, Route, Link, Switch, Redirect} from "react-router-dom";
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
import Edit from "./Edit.js";
|
||||||
|
import Count from "./Count.js";
|
||||||
|
|
||||||
|
class View extends Component{
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<table className="app-table max-width">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colSpan="2"><h1 className="text-no-select">{ this.props.match.params.name }</h1></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colSpan="2">
|
||||||
|
<div>
|
||||||
|
<ul className="navbar" style={{width: "100%"}}>
|
||||||
|
<li><Link to={`${this.props.match.url}/edit`}>edit</Link></li>
|
||||||
|
<li><Link to={`${this.props.match.url}/count`}>count</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<Route path={`${this.props.match.url}/edit`} render={(props) => <Edit {...props} name={this.props.match.params.name}/>} />
|
||||||
|
<Route path={`${this.props.match.url}/count`} render={(props) => <Count {...props} name={this.props.match.params.name}/>} />
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default View;
|
@ -4,7 +4,7 @@ import { BrowserRouter as Router, Route, Link, Switch, Redirect} from "react-rou
|
|||||||
import Index from "./Index/Index.js";
|
import Index from "./Index/Index.js";
|
||||||
import Maths from "./Maths/Maths.js";
|
import Maths from "./Maths/Maths.js";
|
||||||
import Playlists from "./Playlist/Playlists.js";
|
import Playlists from "./Playlist/Playlists.js";
|
||||||
import PlaylistView from "./Playlist/PlaylistView.js";
|
import PlaylistView from "./Playlist/View/View.js";
|
||||||
import Settings from "./Settings/Settings.js";
|
import Settings from "./Settings/Settings.js";
|
||||||
import Admin from "./Admin/Admin.js";
|
import Admin from "./Admin/Admin.js";
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class PlaylistManager extends Component {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr><td><span><Link to="/app">home</Link></span></td></tr>
|
<tr><td><span><Link to="/app">home</Link></span></td></tr>
|
||||||
<tr><td><Link to="/app/playlists">playlists</Link></td></tr>
|
<tr><td><Link to="/app/playlists">playlists</Link></td></tr>
|
||||||
<tr><td><Link to="/app/maths">maths</Link></td></tr>
|
{/* <tr><td><Link to="/app/maths">maths</Link></td></tr> */}
|
||||||
<tr><td><Link to="/app/settings/password">settings</Link></td></tr>
|
<tr><td><Link to="/app/settings/password">settings</Link></td></tr>
|
||||||
{ this.state.type == 'admin' && <tr><td><Link to="/app/admin/lock">admin</Link></td></tr> }
|
{ this.state.type == 'admin' && <tr><td><Link to="/app/admin/lock">admin</Link></td></tr> }
|
||||||
<tr><td><a href="/auth/logout">logout</a></td></tr>
|
<tr><td><a href="/auth/logout">logout</a></td></tr>
|
||||||
|
Loading…
Reference in New Issue
Block a user