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.
 
 
 
 

206 lines
7.4 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/api';
import { WindowState } from '../Windows';
import StoreLinkIcon, { whichStore } from '../../common/StoreLinkIcon';
import TrackTable from '../../tables/ResultsTable';
import { modifyAlbum, modifyTrack } from '../../../lib/saveChanges';
import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import { queryAlbums, queryTracks } from '../../../lib/backend/queries';
import { useParams } from 'react-router';
import { handleNotLoggedIn, NotLoggedInError } from '../../../lib/backend/request';
import { useAuth } from '../../../lib/useAuth';
import { Album, Name, Id, StoreLinks, AlbumRefs, Artist, Tag, Track, ResourceType } from '../../../api/api';
import EditItemDialog, { EditablePropertyType } from '../../common/EditItemDialog';
import EditIcon from '@material-ui/icons/Edit';
export type AlbumMetadata = serverApi.QueryResponseAlbumDetails;
export type AlbumMetadataChanges = serverApi.PatchAlbumRequest;
export interface AlbumWindowState extends WindowState {
id: number,
metadata: AlbumMetadata | null,
pendingChanges: AlbumMetadataChanges | null,
tracksOnAlbum: any[] | null,
}
export enum AlbumWindowStateActions {
SetMetadata = "SetMetadata",
SetPendingChanges = "SetPendingChanges",
SetTracks = "SetTracks",
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.SetTracks:
return { ...state, tracksOnAlbum: action.value }
case AlbumWindowStateActions.Reload:
return { ...state, metadata: null, pendingChanges: null, tracksOnAlbum: null }
default:
throw new Error("Unimplemented AlbumWindow state update.")
}
}
export async function getAlbumMetadata(id: number): Promise<AlbumMetadata> {
let result: any = await queryAlbums(
{
a: QueryLeafBy.AlbumId,
b: id,
leafOp: QueryLeafOp.Equals,
}, 0, 1, serverApi.QueryResponseType.Details
);
return result[0];
}
export default function AlbumWindow(props: {}) {
const { id } = useParams<{ id: string }>();
const [state, dispatch] = useReducer(AlbumWindowReducer, {
id: parseInt(id),
metadata: null,
pendingChanges: null,
tracksOnAlbum: null,
});
return <AlbumWindowControlled state={state} dispatch={dispatch} />
}
export function AlbumWindowControlled(props: {
state: AlbumWindowState,
dispatch: (action: any) => void,
}) {
let { id: albumId, metadata, pendingChanges, tracksOnAlbum } = props.state;
let { dispatch } = props;
let auth = useAuth();
let [editing, setEditing] = useState<boolean>(false);
// Effect to get the album's metadata.
useEffect(() => {
if (metadata === null) {
getAlbumMetadata(albumId)
.then((m: AlbumMetadata) => {
dispatch({
type: AlbumWindowStateActions.SetMetadata,
value: m
});
})
.catch((e: any) => { handleNotLoggedIn(auth, e) })
}
}, [albumId, dispatch, metadata]);
// Effect to get the album's tracks.
useEffect(() => {
if (tracksOnAlbum) { return; }
(async () => {
const tracks = await queryTracks(
{
a: QueryLeafBy.AlbumId,
b: albumId,
leafOp: QueryLeafOp.Equals,
}, 0, -1, serverApi.QueryResponseType.Details
)
.catch((e: any) => { handleNotLoggedIn(auth, e) });
dispatch({
type: AlbumWindowStateActions.SetTracks,
value: tracks,
});
})();
}, [tracksOnAlbum, albumId, dispatch]);
const name = <Typography variant="h4">{metadata?.name || "(Unknown album name)"}</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>
});
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 m={1}>
<IconButton
onClick={() => { setEditing(true); }}
><EditIcon /></IconButton>
</Box>
</Box>}
</Box>
<Box
m={1}
width="80%"
>
<Box display="flex" flexDirection="column" alignItems="left">
<Typography>Tracks in this album in your library:</Typography>
</Box>
{props.state.tracksOnAlbum && <TrackTable
tracks={props.state.tracksOnAlbum}
/>}
{!props.state.tracksOnAlbum && <CircularProgress />}
</Box>
{metadata && <EditItemDialog
open={editing}
onClose={() => { setEditing(false); }}
onSubmit={(v: serverApi.PatchAlbumRequest) => {
// Remove any details about linked resources and leave only their IDs.
let v_modified = {
...v,
tracks: undefined,
artists: undefined,
tags: undefined,
trackIds: v.trackIds || v.tracks?.map(
(a: (Track & Id)) => { return a.id }
) || undefined,
artistIds: v.artistIds || v.artists?.map(
(a: (Artist & Id)) => { return a.id }
) || undefined,
tagIds: v.tagIds || v.tags?.map(
(t: (Tag & Id)) => { return t.id }
) || undefined,
};
modifyAlbum(albumId, v_modified)
.then(() => dispatch({
type: AlbumWindowStateActions.Reload
}))
}}
id={albumId}
metadata={metadata}
editableProperties={[
{ metadataKey: 'name', title: 'Name', type: EditablePropertyType.Text },
]}
defaultExternalLinksQuery={metadata.name}
resourceType={ResourceType.Album}
editStoreLinks={true}
/>}
</Box>
}