parent
8f9e3bd184
commit
9c661d67ee
10 changed files with 270 additions and 33 deletions
@ -0,0 +1,196 @@ |
|||||||
|
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 { SongMetadata } from "./SongWindow"; |
||||||
|
import StoreLinkIcon, { ExternalStore, whichStore } from '../../common/StoreLinkIcon'; |
||||||
|
import CheckIcon from '@material-ui/icons/Check'; |
||||||
|
import CancelIcon from '@material-ui/icons/Cancel'; |
||||||
|
import OpenInNewIcon from '@material-ui/icons/OpenInNew'; |
||||||
|
import DeleteIcon from '@material-ui/icons/Delete'; |
||||||
|
import { $enum } from "ts-enum-util"; |
||||||
|
import { useIntegrations, IntegrationsState, IntegrationState } from '../../../lib/integration/useIntegrations'; |
||||||
|
import { IntegrationFeature, IntegrationSong } from '../../../lib/integration/Integration'; |
||||||
|
import { TabPanel } from '@material-ui/lab'; |
||||||
|
import { v1 } from 'uuid'; |
||||||
|
let _ = require('lodash') |
||||||
|
|
||||||
|
export function ProvideLinksWidget(props: { |
||||||
|
providers: IntegrationState[], |
||||||
|
metadata: SongMetadata, |
||||||
|
store: ExternalStore, |
||||||
|
onChange: (link: string | undefined) => void, |
||||||
|
}) { |
||||||
|
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}` |
||||||
|
) |
||||||
|
let [results, setResults] = useState<IntegrationSong[]>([]); |
||||||
|
|
||||||
|
let selectedProvider: IntegrationState | undefined = selectedProviderIdx !== undefined ? |
||||||
|
props.providers[selectedProviderIdx] : undefined; |
||||||
|
|
||||||
|
let currentLink = props.metadata.storeLinks ? props.metadata.storeLinks.find( |
||||||
|
(l: string) => whichStore(l) === props.store |
||||||
|
) : undefined; |
||||||
|
|
||||||
|
return <Box display="flex" flexDirection="column"> |
||||||
|
<Box display="flex" alignItems="center"> |
||||||
|
<Select |
||||||
|
value={selectedProviderIdx} |
||||||
|
onChange={(e: any) => setSelectedProviderIdx(e.target.value)} |
||||||
|
> |
||||||
|
{props.providers.map((p: IntegrationState, idx: number) => { |
||||||
|
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> |
||||||
|
<FormControl> |
||||||
|
<RadioGroup value={currentLink} onChange={(e: any) => props.onChange(e.target.value)}> |
||||||
|
{results.map((result: IntegrationSong, idx: number) => { |
||||||
|
let pretty = `"${result.title}"
|
||||||
|
${result.artist && ` by ${result.artist.name}`} |
||||||
|
${result.album && ` (${result.album.name})`}`;
|
||||||
|
return <FormControlLabel |
||||||
|
value={result.url || idx} |
||||||
|
control={<Radio checked={(result.url || idx) === currentLink} />} |
||||||
|
label={<Box display="flex" alignItems="center"> |
||||||
|
{pretty} |
||||||
|
<a href={result.url || ""} target="_blank"> |
||||||
|
<IconButton><OpenInNewIcon /></IconButton> |
||||||
|
</a> |
||||||
|
</Box>} |
||||||
|
/> |
||||||
|
})} |
||||||
|
</RadioGroup> |
||||||
|
</FormControl> |
||||||
|
</Box> |
||||||
|
} |
||||||
|
|
||||||
|
export function ExternalLinksEditor(props: { |
||||||
|
metadata: 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) => { |
||||||
|
var maybeLink: string | null = null; |
||||||
|
props.metadata.storeLinks && props.metadata.storeLinks.forEach((link: string) => { |
||||||
|
if (whichStore(link) === store) { |
||||||
|
maybeLink = link; |
||||||
|
} |
||||||
|
}) |
||||||
|
return { |
||||||
|
...prev, |
||||||
|
[store]: maybeLink, |
||||||
|
} |
||||||
|
}, {}) |
||||||
|
|
||||||
|
let store = $enum(ExternalStore).getValues()[selectedIdx]; |
||||||
|
let providers: IntegrationState[] = Array.isArray(integrations.state) ? |
||||||
|
integrations.state.filter( |
||||||
|
(iState: IntegrationState) => ( |
||||||
|
iState.integration.getFeatures().includes(IntegrationFeature.SearchSong) && |
||||||
|
iState.integration.providesStoreLink() === store |
||||||
|
) |
||||||
|
) : []; |
||||||
|
|
||||||
|
return <Box display="flex" width="100%"> |
||||||
|
<Box width="30%"> |
||||||
|
<List> |
||||||
|
|
||||||
|
{$enum(ExternalStore).getValues().map((store: string, idx: number) => { |
||||||
|
let maybeLink = linksSet[store]; |
||||||
|
return <ListItem |
||||||
|
selected={selectedIdx === idx} |
||||||
|
onClick={(e: any) => setSelectedIdx(idx)} |
||||||
|
button |
||||||
|
> |
||||||
|
<ListItemIcon>{linksSet[store] !== null ? <CheckIcon /> : <CancelIcon />}</ListItemIcon> |
||||||
|
<ListItemIcon><StoreLinkIcon whichStore={store} /></ListItemIcon> |
||||||
|
<ListItemText primary={store} /> |
||||||
|
{maybeLink && <a href={maybeLink} target="_blank"> |
||||||
|
<ListItemIcon><IconButton><OpenInNewIcon /></IconButton></ListItemIcon> |
||||||
|
</a>} |
||||||
|
{maybeLink && <ListItemIcon><IconButton |
||||||
|
onClick={() => { |
||||||
|
let newLinks = props.metadata.storeLinks?.filter( |
||||||
|
(l: string) => whichStore(l) !== store |
||||||
|
) |
||||||
|
props.onChange({ |
||||||
|
...props.metadata, |
||||||
|
storeLinks: newLinks, |
||||||
|
}); |
||||||
|
}} |
||||||
|
><DeleteIcon /> |
||||||
|
</IconButton></ListItemIcon>} |
||||||
|
</ListItem> |
||||||
|
})} |
||||||
|
</List> |
||||||
|
</Box> |
||||||
|
<Box ml={2} width="60%"> |
||||||
|
{providers.length === 0 ? |
||||||
|
<Typography>None of your configured integrations provides URL links for {store}.</Typography> : |
||||||
|
<ProvideLinksWidget |
||||||
|
providers={providers} |
||||||
|
metadata={props.metadata} |
||||||
|
store={store} |
||||||
|
onChange={(link: string | undefined) => { |
||||||
|
let removed = props.metadata.storeLinks?.filter( |
||||||
|
(link: string) => whichStore(link) !== store |
||||||
|
) || []; |
||||||
|
let newValue = link ? [...removed, link] : removed; |
||||||
|
if (!_.isEqual(new Set(newValue), new Set(props.metadata.storeLinks || []))) { |
||||||
|
props.onChange({ |
||||||
|
...props.metadata, |
||||||
|
storeLinks: newValue, |
||||||
|
}) |
||||||
|
} |
||||||
|
}} |
||||||
|
/> |
||||||
|
} |
||||||
|
</Box> |
||||||
|
</Box > |
||||||
|
} |
||||||
|
|
||||||
|
export default function EditSongDialog(props: { |
||||||
|
open: boolean, |
||||||
|
onClose: () => void, |
||||||
|
onSubmit: (v: SongMetadata) => void, |
||||||
|
id: number, |
||||||
|
metadata: SongMetadata, |
||||||
|
}) { |
||||||
|
enum EditSongTabs { |
||||||
|
Details = 0, |
||||||
|
ExternalLinks, |
||||||
|
} |
||||||
|
|
||||||
|
let [editingMetadata, setEditingMetadata] = useState<SongMetadata>(props.metadata); |
||||||
|
let [activeTab, setActiveTab] = useState<EditSongTabs>(EditSongTabs.Details); |
||||||
|
|
||||||
|
return <Dialog |
||||||
|
maxWidth="lg" |
||||||
|
fullWidth |
||||||
|
open={props.open} |
||||||
|
onClose={props.onClose} |
||||||
|
disableBackdropClick={true}> |
||||||
|
<ExternalLinksEditor |
||||||
|
metadata={editingMetadata} |
||||||
|
onChange={(v: SongMetadata) => setEditingMetadata(v)} |
||||||
|
/> |
||||||
|
{!_.isEqual(editingMetadata, props.metadata) && <Typography>Changed!</Typography>} |
||||||
|
</Dialog> |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue