parent
0193e42b51
commit
935614d12f
13 changed files with 562 additions and 89 deletions
@ -0,0 +1,93 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { Box, IconButton, TextField } from '@material-ui/core'; |
||||||
|
import EditIcon from '@material-ui/icons/Edit'; |
||||||
|
import CheckIcon from '@material-ui/icons/Check'; |
||||||
|
import UndoIcon from '@material-ui/icons/Undo'; |
||||||
|
import { useTheme } from '@material-ui/core/styles'; |
||||||
|
|
||||||
|
// This component is an editable text. It shows up as normal text,
|
||||||
|
// but will display an edit icon on hover. When clicked, this
|
||||||
|
// enables a text input to make a new suggestion.
|
||||||
|
// The text can show a striked-through version of the old text,
|
||||||
|
// with the new value next to it and an undo button.
|
||||||
|
|
||||||
|
export interface IProps { |
||||||
|
defaultValue: string, |
||||||
|
changedValue: string | null, // Null == not changed
|
||||||
|
editingValue: string | null, // Null == not editing
|
||||||
|
editingLabel: string, |
||||||
|
onChangeEditingValue: (v: string | null) => void, |
||||||
|
onChangeChangedValue: (v: string | null) => void, |
||||||
|
} |
||||||
|
|
||||||
|
export default function EditableText(props: IProps) { |
||||||
|
let editingValue = props.editingValue; |
||||||
|
let defaultValue = props.defaultValue; |
||||||
|
let changedValue = props.changedValue; |
||||||
|
let onChangeEditingValue = props.onChangeEditingValue; |
||||||
|
let onChangeChangedValue = props.onChangeChangedValue; |
||||||
|
let editing = editingValue !== null; |
||||||
|
let editingLabel = props.editingLabel; |
||||||
|
|
||||||
|
const theme = useTheme(); |
||||||
|
|
||||||
|
const [hovering, setHovering] = useState<Boolean>(false); |
||||||
|
|
||||||
|
const editButton = <Box |
||||||
|
visibility={(hovering && !editing) ? "visible" : "hidden"}> |
||||||
|
<IconButton |
||||||
|
onClick={() => onChangeEditingValue(changedValue || defaultValue)} |
||||||
|
> |
||||||
|
<EditIcon /> |
||||||
|
</IconButton> |
||||||
|
</Box> |
||||||
|
|
||||||
|
const discardChangesButton = <Box |
||||||
|
visibility={(hovering && !editing) ? "visible" : "hidden"}> |
||||||
|
<IconButton |
||||||
|
onClick={() => { |
||||||
|
onChangeChangedValue(null); |
||||||
|
onChangeEditingValue(null); |
||||||
|
}} |
||||||
|
> |
||||||
|
<UndoIcon /> |
||||||
|
</IconButton> |
||||||
|
</Box> |
||||||
|
|
||||||
|
if (editing) { |
||||||
|
return <Box display="flex" alignItems="center"> |
||||||
|
<TextField |
||||||
|
variant="outlined" |
||||||
|
value={editingValue || ""} |
||||||
|
label={editingLabel} |
||||||
|
inputProps={{ style: { fontSize: '2rem' } }} |
||||||
|
onChange={(e: any) => onChangeEditingValue(e.target.value)} |
||||||
|
/> |
||||||
|
<IconButton |
||||||
|
onClick={() => { |
||||||
|
onChangeChangedValue(editingValue === defaultValue ? null : editingValue); |
||||||
|
onChangeEditingValue(null); |
||||||
|
}} |
||||||
|
><CheckIcon /></IconButton> |
||||||
|
</Box> |
||||||
|
} else if (changedValue) { |
||||||
|
return <Box |
||||||
|
onMouseEnter={() => setHovering(true)} |
||||||
|
onMouseLeave={() => setHovering(false)} |
||||||
|
display="flex" |
||||||
|
alignItems="center" |
||||||
|
> |
||||||
|
<del style={{ color: theme.palette.text.secondary }}>{defaultValue}</del>→ |
||||||
|
{changedValue} |
||||||
|
{editButton} |
||||||
|
{discardChangesButton} |
||||||
|
</Box> |
||||||
|
} |
||||||
|
|
||||||
|
return <Box |
||||||
|
onMouseEnter={() => setHovering(true)} |
||||||
|
onMouseLeave={() => setHovering(false)} |
||||||
|
display="flex" |
||||||
|
alignItems="center" |
||||||
|
>{defaultValue}{editButton}</Box>; |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { ReactComponent as GPMIcon } from '../../assets/googleplaymusic_icon.svg'; |
||||||
|
|
||||||
|
export enum ExternalStore { |
||||||
|
GooglePlayMusic = "GPM", |
||||||
|
} |
||||||
|
|
||||||
|
export interface IProps { |
||||||
|
whichStore: ExternalStore, |
||||||
|
} |
||||||
|
|
||||||
|
export function whichStore(url: string) { |
||||||
|
if(url.includes('play.google.com')) { |
||||||
|
return ExternalStore.GooglePlayMusic; |
||||||
|
} |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
|
||||||
|
export default function StoreLinkIcon(props: any) { |
||||||
|
const { whichStore, ...restProps } = props; |
||||||
|
|
||||||
|
switch(whichStore) { |
||||||
|
case ExternalStore.GooglePlayMusic: |
||||||
|
return <GPMIcon {...restProps}/>; |
||||||
|
default: |
||||||
|
throw new Error("Unknown external store: " + whichStore) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Box, Button } from '@material-ui/core'; |
||||||
|
|
||||||
|
export default function SubmitChangesButton(props: any) { |
||||||
|
return <Box> |
||||||
|
<Button |
||||||
|
variant="contained" color="secondary" |
||||||
|
> |
||||||
|
Save Changes |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
export const songGetters = { |
||||||
|
getTitle: (song: any) => song.title, |
||||||
|
getId: (song: any) => song.songId, |
||||||
|
getArtistNames: (song: any) => song.artists.map((a: any) => a.name), |
||||||
|
getArtistIds: (song: any) => song.artists.map((a: any) => a.artistId), |
||||||
|
getAlbumNames: (song: any) => song.albums.map((a: any) => a.name), |
||||||
|
getAlbumIds: (song: any) => song.albums.map((a: any) => a.albumId), |
||||||
|
getTagNames: (song: any) => { |
||||||
|
// Recursively resolve the name.
|
||||||
|
const resolveTag = (tag: any) => { |
||||||
|
var r = [tag.name]; |
||||||
|
if (tag.parent) { r.unshift(resolveTag(tag.parent)); } |
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
return song.tags.map((tag: any) => resolveTag(tag)); |
||||||
|
}, |
||||||
|
getTagIds: (song: any) => { |
||||||
|
// Recursively resolve the id.
|
||||||
|
const resolveTag = (tag: any) => { |
||||||
|
var r = [tag.tagId]; |
||||||
|
if (tag.parent) { r.unshift(resolveTag(tag.parent)); } |
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
return song.tags.map((tag: any) => resolveTag(tag)); |
||||||
|
}, |
||||||
|
} |
Loading…
Reference in new issue