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.6 KiB

import React, { useEffect, useState, useReducer } 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';
import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import { queryAlbums, querySongs } from '../../../lib/backend/queries';
import { songGetters } from '../../../lib/songGetters';
import { useParams } from 'react-router';
export type AlbumMetadata = serverApi.AlbumDetails;
export type AlbumMetadataChanges = serverApi.ModifyAlbumRequest;
export interface AlbumWindowState extends WindowState {
id: 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 async function getAlbumMetadata(id: number) {
return (await queryAlbums({
query: {
a: QueryLeafBy.AlbumId,
b: id,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: 1,
}))[0];
}
export default function AlbumWindow(props: {}) {
const { id } = useParams();
const [state, dispatch] = useReducer(AlbumWindowReducer, {
id: id,
metadata: null,
pendingChanges: null,
songGetters: songGetters,
songsOnAlbum: null,
});
return <AlbumWindowControlled state={state} dispatch={dispatch} />
}
export function AlbumWindowControlled(props: {
state: AlbumWindowState,
dispatch: (action: any) => void,
}) {
let { id: albumId, metadata, pendingChanges, songsOnAlbum } = props.state;
let { dispatch } = props;
// Effect to get the album's metadata.
useEffect(() => {
getAlbumMetadata(albumId)
.then((m: AlbumMetadata) => {
dispatch({
type: AlbumWindowStateActions.SetMetadata,
value: m
});
})
}, [albumId, dispatch]);
// Effect to get the album's songs.
useEffect(() => {
if (songsOnAlbum) { return; }
(async () => {
const songs = await querySongs({
query: {
a: QueryLeafBy.AlbumId,
b: albumId,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: -1,
});
dispatch({
type: AlbumWindowStateActions.SetSongs,
value: songs,
});
})();
}, [songsOnAlbum, albumId, dispatch]);
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" rel="noopener noreferrer"
>
<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.id, 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}
/>}
{!props.state.songsOnAlbum && <CircularProgress />}
</Box>
</Box>
}