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