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