|
|
|
@ -1,8 +1,9 @@ |
|
|
|
|
import React, { useEffect, useState } from 'react'; |
|
|
|
|
import { AppBar, Box, Button, Dialog, FormControl, FormControlLabel, IconButton, Link, List, ListItem, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, Select, Tab, Tabs, TextField, Typography } from "@material-ui/core"; |
|
|
|
|
import { AppBar, Box, Button, Dialog, DialogActions, Divider, FormControl, FormControlLabel, IconButton, Link, List, ListItem, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, Select, Tab, Tabs, TextField, Typography } from "@material-ui/core"; |
|
|
|
|
import { SongMetadata } from "./SongWindow"; |
|
|
|
|
import StoreLinkIcon, { ExternalStore, whichStore } from '../../common/StoreLinkIcon'; |
|
|
|
|
import CheckIcon from '@material-ui/icons/Check'; |
|
|
|
|
import SearchIcon from '@material-ui/icons/Search'; |
|
|
|
|
import CancelIcon from '@material-ui/icons/Cancel'; |
|
|
|
|
import OpenInNewIcon from '@material-ui/icons/OpenInNew'; |
|
|
|
|
import DeleteIcon from '@material-ui/icons/Delete'; |
|
|
|
@ -19,13 +20,13 @@ export function ProvideLinksWidget(props: { |
|
|
|
|
store: ExternalStore, |
|
|
|
|
onChange: (link: string | undefined) => void, |
|
|
|
|
}) { |
|
|
|
|
let defaultQuery = `${props.metadata.title}${props.metadata.artists && ` ${props.metadata.artists[0].name}`}${props.metadata.albums && ` ${props.metadata.albums[0].name}`}`; |
|
|
|
|
|
|
|
|
|
let [selectedProviderIdx, setSelectedProviderIdx] = useState<number | undefined>( |
|
|
|
|
props.providers.length > 0 ? 0 : undefined |
|
|
|
|
); |
|
|
|
|
let [query, setQuery] = useState<string>( |
|
|
|
|
`${props.metadata.title}${props.metadata.artists && ` ${props.metadata.artists[0].name}`}${props.metadata.albums && ` ${props.metadata.albums[0].name}`}` |
|
|
|
|
) |
|
|
|
|
let [results, setResults] = useState<IntegrationSong[]>([]); |
|
|
|
|
let [query, setQuery] = useState<string>(defaultQuery) |
|
|
|
|
let [results, setResults] = useState<IntegrationSong[] | undefined>(undefined); |
|
|
|
|
|
|
|
|
|
let selectedProvider: IntegrationState | undefined = selectedProviderIdx !== undefined ? |
|
|
|
|
props.providers[selectedProviderIdx] : undefined; |
|
|
|
@ -34,8 +35,16 @@ export function ProvideLinksWidget(props: { |
|
|
|
|
(l: string) => whichStore(l) === props.store |
|
|
|
|
) : undefined; |
|
|
|
|
|
|
|
|
|
return <Box display="flex" flexDirection="column"> |
|
|
|
|
// Ensure results are cleared when input state changes.
|
|
|
|
|
useEffect(() => { |
|
|
|
|
setResults(undefined); |
|
|
|
|
setQuery(defaultQuery); |
|
|
|
|
}, [props.store, props.providers, props.metadata]) |
|
|
|
|
|
|
|
|
|
return <Box display="flex" flexDirection="column" alignItems="left"> |
|
|
|
|
<Box display="flex" alignItems="center"> |
|
|
|
|
<Typography>Search using:</Typography> |
|
|
|
|
<Box ml={2} /> |
|
|
|
|
<Select |
|
|
|
|
value={selectedProviderIdx} |
|
|
|
|
onChange={(e: any) => setSelectedProviderIdx(e.target.value)} |
|
|
|
@ -44,20 +53,23 @@ export function ProvideLinksWidget(props: { |
|
|
|
|
return <MenuItem value={idx}>{p.properties.name}</MenuItem> |
|
|
|
|
})} |
|
|
|
|
</Select> |
|
|
|
|
<TextField |
|
|
|
|
value={query} |
|
|
|
|
onChange={(e: any) => setQuery(e.target.value)} |
|
|
|
|
/> |
|
|
|
|
<Button |
|
|
|
|
onClick={() => { |
|
|
|
|
selectedProvider?.integration.searchSong(query, 10) |
|
|
|
|
.then((songs: IntegrationSong[]) => setResults(songs)) |
|
|
|
|
}} |
|
|
|
|
>Search</Button> |
|
|
|
|
</Box> |
|
|
|
|
<TextField |
|
|
|
|
value={query} |
|
|
|
|
onChange={(e: any) => setQuery(e.target.value)} |
|
|
|
|
label="Query" |
|
|
|
|
fullWidth |
|
|
|
|
/> |
|
|
|
|
<IconButton |
|
|
|
|
onClick={() => { |
|
|
|
|
selectedProvider?.integration.searchSong(query, 10) |
|
|
|
|
.then((songs: IntegrationSong[]) => setResults(songs)) |
|
|
|
|
}} |
|
|
|
|
><SearchIcon /></IconButton> |
|
|
|
|
{results && results.length > 0 && <Typography>Suggestions:</Typography>} |
|
|
|
|
<FormControl> |
|
|
|
|
<RadioGroup value={currentLink} onChange={(e: any) => props.onChange(e.target.value)}> |
|
|
|
|
{results.map((result: IntegrationSong, idx: number) => { |
|
|
|
|
{results && results.map((result: IntegrationSong, idx: number) => { |
|
|
|
|
let pretty = `"${result.title}"
|
|
|
|
|
${result.artist && ` by ${result.artist.name}`} |
|
|
|
|
${result.album && ` (${result.album.name})`}`;
|
|
|
|
@ -72,22 +84,24 @@ export function ProvideLinksWidget(props: { |
|
|
|
|
</Box>} |
|
|
|
|
/> |
|
|
|
|
})} |
|
|
|
|
{results && results.length === 0 && <Typography>No results were found. Try adjusting the query manually.</Typography>} |
|
|
|
|
</RadioGroup> |
|
|
|
|
</FormControl> |
|
|
|
|
</Box> |
|
|
|
|
</Box > |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function ExternalLinksEditor(props: { |
|
|
|
|
metadata: SongMetadata, |
|
|
|
|
original: SongMetadata, |
|
|
|
|
onChange: (v: SongMetadata) => void, |
|
|
|
|
}) { |
|
|
|
|
let [selectedIdx, setSelectedIdx] = useState<number>(0); |
|
|
|
|
let integrations = useIntegrations(); |
|
|
|
|
|
|
|
|
|
let linksSet: Record<string, string | null> = |
|
|
|
|
$enum(ExternalStore).getValues().reduce((prev: any, store: string) => { |
|
|
|
|
let getLinksSet = (metadata: SongMetadata) => { |
|
|
|
|
return $enum(ExternalStore).getValues().reduce((prev: any, store: string) => { |
|
|
|
|
var maybeLink: string | null = null; |
|
|
|
|
props.metadata.storeLinks && props.metadata.storeLinks.forEach((link: string) => { |
|
|
|
|
metadata.storeLinks && metadata.storeLinks.forEach((link: string) => { |
|
|
|
|
if (whichStore(link) === store) { |
|
|
|
|
maybeLink = link; |
|
|
|
|
} |
|
|
|
@ -96,7 +110,11 @@ export function ExternalLinksEditor(props: { |
|
|
|
|
...prev, |
|
|
|
|
[store]: maybeLink, |
|
|
|
|
} |
|
|
|
|
}, {}) |
|
|
|
|
}, {}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let linksSet: Record<string, string | null> = getLinksSet(props.metadata); |
|
|
|
|
let originalLinksSet: Record<string, string | null> = getLinksSet(props.original); |
|
|
|
|
|
|
|
|
|
let store = $enum(ExternalStore).getValues()[selectedIdx]; |
|
|
|
|
let providers: IntegrationState[] = Array.isArray(integrations.state) ? |
|
|
|
@ -113,16 +131,22 @@ export function ExternalLinksEditor(props: { |
|
|
|
|
|
|
|
|
|
{$enum(ExternalStore).getValues().map((store: string, idx: number) => { |
|
|
|
|
let maybeLink = linksSet[store]; |
|
|
|
|
let color: string | undefined = |
|
|
|
|
(linksSet[store] && !originalLinksSet[store]) ? "lightgreen" : |
|
|
|
|
(!linksSet[store] && originalLinksSet[store]) ? "red" : |
|
|
|
|
(linksSet[store] && originalLinksSet[store] && linksSet[store] !== originalLinksSet[store]) ? "orange" : |
|
|
|
|
undefined; |
|
|
|
|
|
|
|
|
|
return <ListItem |
|
|
|
|
selected={selectedIdx === idx} |
|
|
|
|
onClick={(e: any) => setSelectedIdx(idx)} |
|
|
|
|
button |
|
|
|
|
> |
|
|
|
|
<ListItemIcon>{linksSet[store] !== null ? <CheckIcon /> : <CancelIcon />}</ListItemIcon> |
|
|
|
|
<ListItemIcon>{linksSet[store] !== null ? <CheckIcon style={{ color: color }} /> : <CancelIcon style={{ color: color }} />}</ListItemIcon> |
|
|
|
|
<ListItemIcon><StoreLinkIcon whichStore={store} /></ListItemIcon> |
|
|
|
|
<ListItemText primary={store} /> |
|
|
|
|
<ListItemText style={{ color: color }} primary={store} /> |
|
|
|
|
{maybeLink && <a href={maybeLink} target="_blank"> |
|
|
|
|
<ListItemIcon><IconButton><OpenInNewIcon /></IconButton></ListItemIcon> |
|
|
|
|
<ListItemIcon><IconButton><OpenInNewIcon style={{ color: color }} /></IconButton></ListItemIcon> |
|
|
|
|
</a>} |
|
|
|
|
{maybeLink && <ListItemIcon><IconButton |
|
|
|
|
onClick={() => { |
|
|
|
@ -134,7 +158,7 @@ export function ExternalLinksEditor(props: { |
|
|
|
|
storeLinks: newLinks, |
|
|
|
|
}); |
|
|
|
|
}} |
|
|
|
|
><DeleteIcon /> |
|
|
|
|
><DeleteIcon style={{ color: color }} /> |
|
|
|
|
</IconButton></ListItemIcon>} |
|
|
|
|
</ListItem> |
|
|
|
|
})} |
|
|
|
@ -178,7 +202,6 @@ export default function EditSongDialog(props: { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let [editingMetadata, setEditingMetadata] = useState<SongMetadata>(props.metadata); |
|
|
|
|
let [activeTab, setActiveTab] = useState<EditSongTabs>(EditSongTabs.Details); |
|
|
|
|
|
|
|
|
|
return <Dialog |
|
|
|
|
maxWidth="lg" |
|
|
|
@ -186,11 +209,25 @@ export default function EditSongDialog(props: { |
|
|
|
|
open={props.open} |
|
|
|
|
onClose={props.onClose} |
|
|
|
|
disableBackdropClick={true}> |
|
|
|
|
<Typography variant="h5">Properties</Typography> |
|
|
|
|
<Typography>Under construction</Typography> |
|
|
|
|
<Divider /> |
|
|
|
|
<Typography variant="h5">External Links</Typography> |
|
|
|
|
<ExternalLinksEditor |
|
|
|
|
metadata={editingMetadata} |
|
|
|
|
original={props.metadata} |
|
|
|
|
onChange={(v: SongMetadata) => setEditingMetadata(v)} |
|
|
|
|
/> |
|
|
|
|
{!_.isEqual(editingMetadata, props.metadata) && <Typography>Changed!</Typography>} |
|
|
|
|
<Divider /> |
|
|
|
|
{!_.isEqual(editingMetadata, props.metadata) && <DialogActions> |
|
|
|
|
<Button variant="contained" color="secondary" |
|
|
|
|
onClick={() => { |
|
|
|
|
props.onSubmit(editingMetadata); |
|
|
|
|
props.onClose(); |
|
|
|
|
}}>Save all changes</Button> |
|
|
|
|
<Button variant="outlined" |
|
|
|
|
onClick={() => setEditingMetadata(props.metadata)}>Discard changes</Button> |
|
|
|
|
</DialogActions>} |
|
|
|
|
</Dialog> |
|
|
|
|
|
|
|
|
|
} |