You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
6.4 KiB
193 lines
6.4 KiB
import React, { useEffect, useState } from 'react'; |
|
import { Box, Typography, IconButton, Button, CircularProgress } from '@material-ui/core'; |
|
import PersonIcon from '@material-ui/icons/Person'; |
|
import * as serverApi from '../../../api'; |
|
import { WindowState } from '../Windows'; |
|
import StoreLinkIcon, { whichStore } from '../../common/StoreLinkIcon'; |
|
import EditableText from '../../common/EditableText'; |
|
import SubmitChangesButton from '../../common/SubmitChangesButton'; |
|
import SongTable, { SongGetters } from '../../tables/ResultsTable'; |
|
import { saveArtistChanges } from '../../../lib/saveChanges'; |
|
import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query'; |
|
import { queryArtists, querySongs } from '../../../lib/backend/queries'; |
|
var _ = require('lodash'); |
|
|
|
export type ArtistMetadata = serverApi.ArtistDetails; |
|
export type ArtistMetadataChanges = serverApi.ModifyArtistRequest; |
|
|
|
export interface ArtistWindowState extends WindowState { |
|
artistId: number, |
|
metadata: ArtistMetadata | null, |
|
pendingChanges: ArtistMetadataChanges | null, |
|
songsByArtist: any[] | null, |
|
songGetters: SongGetters, |
|
} |
|
|
|
export enum ArtistWindowStateActions { |
|
SetMetadata = "SetMetadata", |
|
SetPendingChanges = "SetPendingChanges", |
|
SetSongs = "SetSongs", |
|
Reload = "Reload", |
|
} |
|
|
|
export function ArtistWindowReducer(state: ArtistWindowState, action: any) { |
|
switch (action.type) { |
|
case ArtistWindowStateActions.SetMetadata: |
|
return { ...state, metadata: action.value } |
|
case ArtistWindowStateActions.SetPendingChanges: |
|
return { ...state, pendingChanges: action.value } |
|
case ArtistWindowStateActions.SetSongs: |
|
return { ...state, songsByArtist: action.value } |
|
case ArtistWindowStateActions.Reload: |
|
return { ...state, metadata: null, pendingChanges: null, songsByArtist: null } |
|
default: |
|
throw new Error("Unimplemented ArtistWindow state update.") |
|
} |
|
} |
|
|
|
export interface IProps { |
|
state: ArtistWindowState, |
|
dispatch: (action: any) => void, |
|
mainDispatch: (action: any) => void, |
|
} |
|
|
|
export async function getArtistMetadata(id: number) { |
|
return (await queryArtists({ |
|
query: { |
|
a: QueryLeafBy.ArtistId, |
|
b: id, |
|
leafOp: QueryLeafOp.Equals, |
|
}, |
|
offset: 0, |
|
limit: 1, |
|
}))[0]; |
|
} |
|
|
|
export default function ArtistWindow(props: IProps) { |
|
let metadata = props.state.metadata; |
|
let pendingChanges = props.state.pendingChanges; |
|
|
|
// Effect to get the artist's metadata. |
|
useEffect(() => { |
|
getArtistMetadata(props.state.artistId) |
|
.then((m: ArtistMetadata) => { |
|
props.dispatch({ |
|
type: ArtistWindowStateActions.SetMetadata, |
|
value: m |
|
}); |
|
}) |
|
}, [metadata?.name]); |
|
|
|
// Effect to get the artist's songs. |
|
useEffect(() => { |
|
if (props.state.songsByArtist) { return; } |
|
|
|
(async () => { |
|
const songs = await querySongs({ |
|
query: { |
|
a: QueryLeafBy.ArtistId, |
|
b: props.state.artistId, |
|
leafOp: QueryLeafOp.Equals, |
|
}, |
|
offset: 0, |
|
limit: -1, |
|
}); |
|
props.dispatch({ |
|
type: ArtistWindowStateActions.SetSongs, |
|
value: songs, |
|
}); |
|
})(); |
|
}, [props.state.songsByArtist]); |
|
|
|
const [editingName, setEditingName] = useState<string | null>(null); |
|
const name = <Typography variant="h4"><EditableText |
|
defaultValue={metadata?.name || "(Unknown name)"} |
|
changedValue={pendingChanges?.name || null} |
|
editingValue={editingName} |
|
editingLabel="Name" |
|
onChangeEditingValue={(v: string | null) => setEditingName(v)} |
|
onChangeChangedValue={(v: string | null) => { |
|
let newVal: any = { ...pendingChanges }; |
|
if (v) { newVal.name = v } |
|
else { delete newVal.name } |
|
props.dispatch({ |
|
type: ArtistWindowStateActions.SetPendingChanges, |
|
value: newVal, |
|
}) |
|
}} |
|
/></Typography> |
|
|
|
const storeLinks = metadata?.storeLinks && metadata?.storeLinks.map((link: string) => { |
|
const store = whichStore(link); |
|
return store && <a |
|
href={link} target="_blank" |
|
> |
|
<IconButton><StoreLinkIcon |
|
whichStore={store} |
|
style={{ height: '40px', width: '40px' }} |
|
/> |
|
</IconButton> |
|
</a> |
|
}); |
|
|
|
const [applying, setApplying] = useState(false); |
|
const maybeSubmitButton = pendingChanges && Object.keys(pendingChanges).length > 0 && |
|
<Box> |
|
<SubmitChangesButton onClick={() => { |
|
setApplying(true); |
|
saveArtistChanges(props.state.artistId, pendingChanges || {}) |
|
.then(() => { |
|
setApplying(false); |
|
props.dispatch({ |
|
type: ArtistWindowStateActions.Reload |
|
}) |
|
}) |
|
}} /> |
|
{applying && <CircularProgress />} |
|
</Box> |
|
|
|
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
|
<Box |
|
m={1} |
|
mt={4} |
|
width="80%" |
|
> |
|
<PersonIcon style={{ fontSize: 80 }} /> |
|
</Box> |
|
<Box |
|
m={1} |
|
width="80%" |
|
> |
|
{metadata && <Box> |
|
<Box m={2}> |
|
{name} |
|
</Box> |
|
<Box m={1}> |
|
<Box display="flex" alignItems="center" m={0.5}> |
|
{storeLinks} |
|
</Box> |
|
</Box> |
|
</Box>} |
|
</Box> |
|
<Box |
|
m={1} |
|
width="80%" |
|
> |
|
{maybeSubmitButton} |
|
</Box> |
|
<Box |
|
m={1} |
|
width="80%" |
|
> |
|
<Box display="flex" flexDirection="column" alignItems="left"> |
|
<Typography>Songs by this artist in your library:</Typography> |
|
</Box> |
|
{props.state.songsByArtist && <SongTable |
|
songs={props.state.songsByArtist} |
|
songGetters={props.state.songGetters} |
|
mainDispatch={props.mainDispatch} |
|
/>} |
|
{!props.state.songsByArtist && <CircularProgress />} |
|
</Box> |
|
</Box> |
|
} |