parent
6b89e618ce
commit
bef55a6994
12 changed files with 323 additions and 33 deletions
@ -0,0 +1,251 @@ |
|||||||
|
import React, { useEffect, useState } from 'react'; |
||||||
|
import { WindowState } from "./Windows" |
||||||
|
import { MusicStore, whichStore } from '../../lib/MusicStore'; |
||||||
|
import { songGetters, SongGetters } from '../../lib/songGetters'; |
||||||
|
import { Typography, Box, CircularProgress, Paper } from '@material-ui/core'; |
||||||
|
import EqualizerIcon from '@material-ui/icons/Equalizer'; |
||||||
|
import * as serverApi from '../../api'; |
||||||
|
|
||||||
|
var _ = require('lodash'); |
||||||
|
|
||||||
|
export interface SongStatistics { |
||||||
|
numberOf: number, |
||||||
|
noAlbum: number, |
||||||
|
noArtist: number, |
||||||
|
perStore: Record<MusicStore, number>, |
||||||
|
} |
||||||
|
|
||||||
|
export interface AlbumStatistics { |
||||||
|
numberOf: number, |
||||||
|
noSongs: number, |
||||||
|
noArtist: number, |
||||||
|
perStore: Record<MusicStore, number>, |
||||||
|
} |
||||||
|
|
||||||
|
export interface ArtistStatistics { |
||||||
|
numberOf: number, |
||||||
|
noSongs: number, |
||||||
|
noAlbums: number, |
||||||
|
perStore: Record<MusicStore, number>, |
||||||
|
} |
||||||
|
|
||||||
|
export interface TagStatistics { |
||||||
|
numberOf: number, |
||||||
|
noItems: number, |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export interface Statistics { |
||||||
|
songStats: SongStatistics, |
||||||
|
artistStats: ArtistStatistics, |
||||||
|
albumStats: AlbumStatistics, |
||||||
|
tagStats: TagStatistics, |
||||||
|
} |
||||||
|
export function newStatistics(): Statistics { |
||||||
|
return { |
||||||
|
songStats: { |
||||||
|
numberOf: 0, |
||||||
|
noAlbum: 0, |
||||||
|
noArtist: 0, |
||||||
|
perStore: { |
||||||
|
[MusicStore.GooglePlayMusic]: 0 |
||||||
|
} |
||||||
|
}, |
||||||
|
artistStats: { |
||||||
|
numberOf: 0, |
||||||
|
noAlbums: 0, |
||||||
|
noSongs: 0, |
||||||
|
perStore: { |
||||||
|
[MusicStore.GooglePlayMusic]: 0 |
||||||
|
} |
||||||
|
}, |
||||||
|
albumStats: { |
||||||
|
numberOf: 0, |
||||||
|
noArtist: 0, |
||||||
|
noSongs: 0, |
||||||
|
perStore: { |
||||||
|
[MusicStore.GooglePlayMusic]: 0 |
||||||
|
} |
||||||
|
}, |
||||||
|
tagStats: { |
||||||
|
numberOf: 0, |
||||||
|
noItems: 0, |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export interface StatisticsWindowState extends WindowState { |
||||||
|
stats: Statistics | null, |
||||||
|
retrievingStats: boolean, |
||||||
|
} |
||||||
|
|
||||||
|
export enum StatisticsWindowStateActions { |
||||||
|
Reset = "Reset", |
||||||
|
AddSongs = "AddSongs", |
||||||
|
SetRetrieving = "SetRetrieving", |
||||||
|
} |
||||||
|
|
||||||
|
export function StatisticsWindowReducer(state: StatisticsWindowState, action: any) { |
||||||
|
switch (action.type) { |
||||||
|
case StatisticsWindowStateActions.Reset: |
||||||
|
return { |
||||||
|
...state, |
||||||
|
stats: null, |
||||||
|
retrievingStats: true, |
||||||
|
}; |
||||||
|
case StatisticsWindowStateActions.AddSongs: |
||||||
|
return { |
||||||
|
...state, |
||||||
|
stats: addSongs(state.stats, action.songs, songGetters) |
||||||
|
}; |
||||||
|
case StatisticsWindowStateActions.SetRetrieving: |
||||||
|
return { |
||||||
|
...state, |
||||||
|
retrievingStats: action.value, |
||||||
|
}; |
||||||
|
default: |
||||||
|
throw new Error("Unimplemented SongWindow state update.") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function addSongs(stats: Statistics | null, songs: any[], songGetters: SongGetters) { |
||||||
|
var r: Statistics = stats ? _.cloneDeep(stats) : newStatistics(); |
||||||
|
|
||||||
|
songs.forEach((song: any) => { |
||||||
|
r.songStats.numberOf++; |
||||||
|
if (songGetters.getAlbumIds(song) === []) { r.songStats.noAlbum++; } |
||||||
|
if (songGetters.getArtistIds(song) === []) { r.songStats.noArtist++; } |
||||||
|
songGetters.getStoreLinks(song).forEach((link: string) => { |
||||||
|
const which = whichStore(link); |
||||||
|
if (which) { |
||||||
|
r.songStats.perStore[which]++; |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
export async function gatherStats(dispatch: (action: any) => void) { |
||||||
|
// Gather 50 songs at a time
|
||||||
|
for (let i = 0; ; i += 50) { |
||||||
|
var q: serverApi.QueryRequest = { |
||||||
|
query: {}, |
||||||
|
offsetsLimits: { |
||||||
|
songOffset: i, |
||||||
|
songLimit: 50, |
||||||
|
}, |
||||||
|
ordering: { |
||||||
|
orderBy: { |
||||||
|
type: serverApi.OrderByType.Name, |
||||||
|
}, |
||||||
|
ascending: true, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const requestOpts = { |
||||||
|
method: 'POST', |
||||||
|
headers: { 'Content-Type': 'application/json' }, |
||||||
|
body: JSON.stringify(q), |
||||||
|
}; |
||||||
|
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) |
||||||
|
let json: any = await response.json(); |
||||||
|
const songs = json.songs; |
||||||
|
|
||||||
|
if (!songs.length || songs.length === 0) { |
||||||
|
// No more songs.
|
||||||
|
break; |
||||||
|
} |
||||||
|
dispatch({ |
||||||
|
type: StatisticsWindowStateActions.AddSongs, |
||||||
|
songs: songs, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function StatisticsDisplay(props: { stats: Statistics }) { |
||||||
|
return <Box display="flex" alignItems="center"> |
||||||
|
<Box m={2}> |
||||||
|
<Paper> |
||||||
|
<Box p={2}> |
||||||
|
<Box mb={1}><Typography variant="h4">Songs</Typography></Box> |
||||||
|
<Typography>Total: {props.stats.songStats.numberOf}</Typography> |
||||||
|
<Typography>Without album: {props.stats.songStats.noAlbum}</Typography> |
||||||
|
<Typography>Without artist: {props.stats.songStats.noArtist}</Typography> |
||||||
|
<Typography>Linked to Google Play Music: {props.stats.songStats.perStore[MusicStore.GooglePlayMusic]}</Typography> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Box> |
||||||
|
<Box m={2}> |
||||||
|
<Paper> |
||||||
|
<Box p={2}> |
||||||
|
<Box mb={1}><Typography variant="h4">Artists</Typography></Box> |
||||||
|
<Typography>Total: {props.stats.artistStats.numberOf}</Typography> |
||||||
|
<Typography>Without album: {props.stats.artistStats.noAlbums}</Typography> |
||||||
|
<Typography>Without songs: {props.stats.artistStats.noSongs}</Typography> |
||||||
|
<Typography>Linked to Google Play Music: {props.stats.artistStats.perStore[MusicStore.GooglePlayMusic]}</Typography> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Box> |
||||||
|
<Box m={2}> |
||||||
|
<Paper> |
||||||
|
<Box p={2}> |
||||||
|
<Box mb={1}><Typography variant="h4">Albums</Typography></Box> |
||||||
|
<Typography>Total: {props.stats.albumStats.numberOf}</Typography> |
||||||
|
<Typography>Without album: {props.stats.albumStats.noArtist}</Typography> |
||||||
|
<Typography>Without songs: {props.stats.albumStats.noSongs}</Typography> |
||||||
|
<Typography>Linked to Google Play Music: {props.stats.albumStats.perStore[MusicStore.GooglePlayMusic]}</Typography> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Box> |
||||||
|
<Box m={2}> |
||||||
|
<Paper> |
||||||
|
<Box p={2}> |
||||||
|
<Box mb={1}><Typography variant="h4">Tags</Typography></Box> |
||||||
|
<Typography>Total: {props.stats.tagStats.numberOf}</Typography> |
||||||
|
<Typography>Unused: {props.stats.tagStats.noItems}</Typography> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Box> |
||||||
|
</Box > |
||||||
|
} |
||||||
|
|
||||||
|
export interface IProps { |
||||||
|
state: StatisticsWindowState, |
||||||
|
dispatch: (action: any) => void, |
||||||
|
mainDispatch: (action: any) => void, |
||||||
|
} |
||||||
|
|
||||||
|
export default function StatisticsWindow(props: IProps) { |
||||||
|
useEffect(() => { |
||||||
|
if (!props.state.retrievingStats) return; |
||||||
|
|
||||||
|
props.dispatch({ |
||||||
|
type: StatisticsWindowStateActions.Reset |
||||||
|
}) |
||||||
|
gatherStats(props.dispatch).then(() => { |
||||||
|
props.dispatch({ |
||||||
|
type: StatisticsWindowStateActions.SetRetrieving, |
||||||
|
value: false, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, [props.state.retrievingStats]) |
||||||
|
|
||||||
|
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
||||||
|
<Box |
||||||
|
m={1} |
||||||
|
mt={4} |
||||||
|
width="80%" |
||||||
|
> |
||||||
|
<EqualizerIcon style={{ fontSize: 80 }} /> |
||||||
|
</Box> |
||||||
|
<Box |
||||||
|
m={1} |
||||||
|
mt={4} |
||||||
|
width="80%" |
||||||
|
> |
||||||
|
<Box visibility={props.state.retrievingStats ? "visible" : "hidden"}><CircularProgress /></Box> |
||||||
|
{props.state.stats && <StatisticsDisplay stats={props.state.stats} />} |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
export enum MusicStore { |
||||||
|
GooglePlayMusic = "GPM", |
||||||
|
} |
||||||
|
export function whichStore(url: string) { |
||||||
|
if(url.includes('play.google.com')) { |
||||||
|
return MusicStore.GooglePlayMusic; |
||||||
|
} |
||||||
|
return undefined; |
||||||
|
} |
Loading…
Reference in new issue