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

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>
}