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.
203 lines
6.5 KiB
203 lines
6.5 KiB
import React, { useEffect, useState } from 'react'; |
|
import { Box, Typography, IconButton, Button, CircularProgress } from '@material-ui/core'; |
|
import AudiotrackIcon from '@material-ui/icons/Audiotrack'; |
|
import PersonIcon from '@material-ui/icons/Person'; |
|
import AlbumIcon from '@material-ui/icons/Album'; |
|
import * as serverApi from '../../api'; |
|
import { WindowState } from './Windows'; |
|
import { ArtistMetadata } from './ArtistWindow'; |
|
import { AlbumMetadata } from './AlbumWindow'; |
|
import StoreLinkIcon, { whichStore } from '../common/StoreLinkIcon'; |
|
import EditableText from '../common/EditableText'; |
|
import SubmitChangesButton from '../common/SubmitChangesButton'; |
|
import { saveSongChanges } from '../../lib/saveChanges'; |
|
|
|
export type SongMetadata = serverApi.SongDetails; |
|
export type SongMetadataChanges = serverApi.ModifySongRequest; |
|
|
|
export interface SongWindowState extends WindowState { |
|
songId: number, |
|
metadata: SongMetadata | null, |
|
pendingChanges: SongMetadataChanges | null, |
|
} |
|
|
|
export enum SongWindowStateActions { |
|
SetMetadata = "SetMetadata", |
|
SetPendingChanges = "SetPendingChanges", |
|
Reload = "Reload", |
|
} |
|
|
|
export function SongWindowReducer(state: SongWindowState, action: any) { |
|
switch (action.type) { |
|
case SongWindowStateActions.SetMetadata: |
|
return { ...state, metadata: action.value } |
|
case SongWindowStateActions.SetPendingChanges: |
|
return { ...state, pendingChanges: action.value } |
|
case SongWindowStateActions.Reload: |
|
return { ...state, metadata: null, pendingChanges: null } |
|
default: |
|
throw new Error("Unimplemented SongWindow state update.") |
|
} |
|
} |
|
|
|
export interface IProps { |
|
state: SongWindowState, |
|
dispatch: (action: any) => void, |
|
mainDispatch: (action: any) => void, |
|
} |
|
|
|
export async function getSongMetadata(id: number) { |
|
const query = { |
|
prop: serverApi.QueryElemProperty.songId, |
|
propOperand: id, |
|
propOperator: serverApi.QueryFilterOp.Eq, |
|
}; |
|
|
|
var q: serverApi.QueryRequest = { |
|
query: query, |
|
offsetsLimits: { |
|
songOffset: 0, |
|
songLimit: 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 song = json.songs[0]; |
|
return song; |
|
})(); |
|
} |
|
|
|
export default function SongWindow(props: IProps) { |
|
let metadata = props.state.metadata; |
|
let pendingChanges = props.state.pendingChanges; |
|
|
|
useEffect(() => { |
|
getSongMetadata(props.state.songId) |
|
.then((m: SongMetadata) => { |
|
props.dispatch({ |
|
type: SongWindowStateActions.SetMetadata, |
|
value: m |
|
}); |
|
}) |
|
}, [metadata?.title]); |
|
|
|
const [editingTitle, setEditingTitle] = useState<string | null>(null); |
|
const title = <Typography variant="h4"><EditableText |
|
defaultValue={metadata?.title || "(Unknown title)"} |
|
changedValue={pendingChanges?.title || null} |
|
editingValue={editingTitle} |
|
editingLabel="Title" |
|
onChangeEditingValue={(v: string | null) => setEditingTitle(v)} |
|
onChangeChangedValue={(v: string | null) => { |
|
let newVal: any = { ...pendingChanges }; |
|
if (v) { newVal.title = v } |
|
else { delete newVal.title } |
|
props.dispatch({ |
|
type: SongWindowStateActions.SetPendingChanges, |
|
value: newVal, |
|
}) |
|
}} |
|
/></Typography> |
|
|
|
const artists = metadata?.artists && metadata?.artists.map((artist: ArtistMetadata) => { |
|
return <Typography> |
|
{artist.name} |
|
</Typography> |
|
}); |
|
|
|
const albums = metadata?.albums && metadata?.albums.map((album: AlbumMetadata) => { |
|
return <Typography> |
|
{album.name} |
|
</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); |
|
saveSongChanges(props.state.songId, pendingChanges || {}) |
|
.then(() => { |
|
setApplying(false); |
|
props.dispatch({ |
|
type: SongWindowStateActions.Reload |
|
}) |
|
}) |
|
}} /> |
|
{applying && <CircularProgress />} |
|
</Box> |
|
|
|
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
|
<Box |
|
m={1} |
|
mt={4} |
|
width="80%" |
|
> |
|
<AudiotrackIcon style={{ fontSize: 80 }} /> |
|
</Box> |
|
<Box |
|
m={1} |
|
width="80%" |
|
> |
|
{metadata && <Box> |
|
<Box m={2}> |
|
{title} |
|
</Box> |
|
<Box m={0.5}> |
|
<Box display="flex" alignItems="center" m={0.5}> |
|
<PersonIcon /> |
|
<Box m={0.5}> |
|
{artists} |
|
</Box> |
|
</Box> |
|
</Box> |
|
<Box m={0.5}> |
|
<Box display="flex" alignItems="center" m={0.5}> |
|
<AlbumIcon /> |
|
<Box m={0.5}> |
|
{albums} |
|
</Box> |
|
</Box> |
|
</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> |
|
} |