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.
231 lines
7.4 KiB
231 lines
7.4 KiB
import React, { useEffect, useState } from 'react'; |
|
import { Box, Typography, IconButton, CircularProgress } from '@material-ui/core'; |
|
import AlbumIcon from '@material-ui/icons/Album'; |
|
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 { saveAlbumChanges } from '../../lib/saveChanges'; |
|
var _ = require('lodash'); |
|
|
|
export type AlbumMetadata = serverApi.AlbumDetails; |
|
export type AlbumMetadataChanges = serverApi.ModifyAlbumRequest; |
|
|
|
export interface AlbumWindowState extends WindowState { |
|
albumId: number, |
|
metadata: AlbumMetadata | null, |
|
pendingChanges: AlbumMetadataChanges | null, |
|
songsOnAlbum: any[] | null, |
|
songGetters: SongGetters, |
|
} |
|
|
|
export enum AlbumWindowStateActions { |
|
SetMetadata = "SetMetadata", |
|
SetPendingChanges = "SetPendingChanges", |
|
SetSongs = "SetSongs", |
|
Reload = "Reload", |
|
} |
|
|
|
export function AlbumWindowReducer(state: AlbumWindowState, action: any) { |
|
switch (action.type) { |
|
case AlbumWindowStateActions.SetMetadata: |
|
return { ...state, metadata: action.value } |
|
case AlbumWindowStateActions.SetPendingChanges: |
|
return { ...state, pendingChanges: action.value } |
|
case AlbumWindowStateActions.SetSongs: |
|
return { ...state, songsOnAlbum: action.value } |
|
case AlbumWindowStateActions.Reload: |
|
return { ...state, metadata: null, pendingChanges: null, songsOnAlbum: null } |
|
default: |
|
throw new Error("Unimplemented AlbumWindow state update.") |
|
} |
|
} |
|
|
|
export interface IProps { |
|
state: AlbumWindowState, |
|
dispatch: (action: any) => void, |
|
mainDispatch: (action: any) => void, |
|
} |
|
|
|
export async function getAlbumMetadata(id: number) { |
|
const query = { |
|
prop: serverApi.QueryElemProperty.albumId, |
|
propOperand: id, |
|
propOperator: serverApi.QueryFilterOp.Eq, |
|
}; |
|
|
|
var q: serverApi.QueryRequest = { |
|
query: query, |
|
offsetsLimits: { |
|
albumOffset: 0, |
|
albumLimit: 1, |
|
}, |
|
ordering: { |
|
orderBy: { |
|
type: serverApi.OrderByType.Name, |
|
}, |
|
ascending: true, |
|
}, |
|
}; |
|
|
|
const requestOpts = { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify(q), |
|
}; |
|
|
|
return (async () => { |
|
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) |
|
let json: any = await response.json(); |
|
let album = json.albums[0]; |
|
return album; |
|
})(); |
|
} |
|
|
|
export default function AlbumWindow(props: IProps) { |
|
let metadata = props.state.metadata; |
|
let pendingChanges = props.state.pendingChanges; |
|
|
|
// Effect to get the album's metadata. |
|
useEffect(() => { |
|
getAlbumMetadata(props.state.albumId) |
|
.then((m: AlbumMetadata) => { |
|
props.dispatch({ |
|
type: AlbumWindowStateActions.SetMetadata, |
|
value: m |
|
}); |
|
}) |
|
}, [metadata?.name]); |
|
|
|
// Effect to get the album's songs. |
|
useEffect(() => { |
|
if (props.state.songsOnAlbum) { return; } |
|
|
|
var q: serverApi.QueryRequest = { |
|
query: { |
|
prop: serverApi.QueryElemProperty.albumId, |
|
propOperator: serverApi.QueryFilterOp.Eq, |
|
propOperand: props.state.albumId, |
|
}, |
|
offsetsLimits: { |
|
songOffset: 0, |
|
songLimit: 100, |
|
}, |
|
ordering: { |
|
orderBy: { |
|
type: serverApi.OrderByType.Name, |
|
}, |
|
ascending: true, |
|
}, |
|
}; |
|
|
|
const requestOpts = { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify(q), |
|
}; |
|
|
|
(async () => { |
|
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) |
|
let json: any = await response.json(); |
|
props.dispatch({ |
|
type: AlbumWindowStateActions.SetSongs, |
|
value: json.songs, |
|
}); |
|
})(); |
|
}, [props.state.songsOnAlbum]); |
|
|
|
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: AlbumWindowStateActions.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); |
|
saveAlbumChanges(props.state.albumId, pendingChanges || {}) |
|
.then(() => { |
|
setApplying(false); |
|
props.dispatch({ |
|
type: AlbumWindowStateActions.Reload |
|
}) |
|
}) |
|
}} /> |
|
{applying && <CircularProgress />} |
|
</Box> |
|
|
|
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
|
<Box |
|
m={1} |
|
mt={4} |
|
width="80%" |
|
> |
|
<AlbumIcon 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 in this album in your library:</Typography> |
|
</Box> |
|
{props.state.songsOnAlbum && <SongTable |
|
songs={props.state.songsOnAlbum} |
|
songGetters={props.state.songGetters} |
|
mainDispatch={props.mainDispatch} |
|
/>} |
|
{!props.state.songsOnAlbum && <CircularProgress />} |
|
</Box> |
|
</Box> |
|
} |