From 6b89e618cea841dfe6ababa9dbd7730bc4a4f13f Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Thu, 24 Sep 2020 08:28:07 +0000 Subject: [PATCH] Details pages for Song, Artist, Album, Tag and back-end PUT fixes (#21) Details pages for Song, Artist, Album, Tag and back-end PUT fixes Reviewed-on: https://gitea.octiron.soleus.nu/sander/MuDBase/pulls/21 --- client/src/components/MainWindow.tsx | 29 ++- client/src/components/appbar/AppBar.tsx | 9 +- client/src/components/common/EditableText.tsx | 93 +++++++++ .../src/components/common/StoreLinkIcon.tsx | 28 +++ .../components/common/SubmitChangesButton.tsx | 13 ++ client/src/components/tables/ResultsTable.tsx | 30 +-- client/src/components/windows/AlbumWindow.tsx | 154 +++++++++++++-- .../src/components/windows/ArtistWindow.tsx | 154 +++++++++++++-- client/src/components/windows/QueryWindow.tsx | 33 +--- client/src/components/windows/SongWindow.tsx | 126 +++++++++++-- client/src/components/windows/TagWindow.tsx | 177 +++++++++++++++++- client/src/components/windows/Windows.tsx | 19 +- client/src/lib/saveChanges.tsx | 57 ++++++ client/src/lib/songGetters.tsx | 28 +++ .../endpoints/ModifyAlbumEndpointHandler.ts | 41 ++-- .../endpoints/ModifyArtistEndpointHandler.ts | 30 +-- server/endpoints/ModifySongEndpointHandler.ts | 52 ++--- 17 files changed, 909 insertions(+), 164 deletions(-) create mode 100644 client/src/components/common/EditableText.tsx create mode 100644 client/src/components/common/StoreLinkIcon.tsx create mode 100644 client/src/components/common/SubmitChangesButton.tsx create mode 100644 client/src/lib/saveChanges.tsx create mode 100644 client/src/lib/songGetters.tsx diff --git a/client/src/components/MainWindow.tsx b/client/src/components/MainWindow.tsx index 970cef6..d3b73f1 100644 --- a/client/src/components/MainWindow.tsx +++ b/client/src/components/MainWindow.tsx @@ -1,5 +1,5 @@ -import React, { useReducer, useState, Reducer } from 'react'; -import { ThemeProvider, CssBaseline, createMuiTheme, withWidth } from '@material-ui/core'; +import React, { useReducer, Reducer } from 'react'; +import { ThemeProvider, CssBaseline, createMuiTheme } from '@material-ui/core'; import { grey } from '@material-ui/core/colors'; import AppBar from './appbar/AppBar'; import QueryWindow from './windows/QueryWindow'; @@ -48,7 +48,6 @@ export function MainWindowReducer(state: MainWindowState, action: any) { activeTab: state.activeTab >= (newSize - 1) ? (newSize - 1) : state.activeTab, } case MainWindowStateActions.AddTab: - console.log("Add tab: ", action) return { ...state, tabStates: [...state.tabStates, action.tabState], @@ -65,17 +64,33 @@ export function MainWindowReducer(state: MainWindowState, action: any) { }) } default: - throw new Error("Unimplemented QueryWindow state update.") + throw new Error("Unimplemented MainWindow state update.") } } export default function MainWindow(props: any) { const [state, dispatch] = useReducer(MainWindowReducer, { tabStates: [ - newWindowState[WindowType.Query]() + newWindowState[WindowType.Query](), + newWindowState[WindowType.Song](), + newWindowState[WindowType.Album](), + newWindowState[WindowType.Artist](), + newWindowState[WindowType.Tag](), + ], + tabReducers: [ + newWindowReducer[WindowType.Query], + newWindowReducer[WindowType.Song], + newWindowReducer[WindowType.Album], + newWindowReducer[WindowType.Artist], + newWindowReducer[WindowType.Tag], + ], + tabTypes: [ + WindowType.Query, + WindowType.Song, + WindowType.Album, + WindowType.Artist, + WindowType.Tag, ], - tabReducers: [newWindowReducer[WindowType.Query]], - tabTypes: [WindowType.Query], activeTab: 0 }) diff --git a/client/src/components/appbar/AppBar.tsx b/client/src/components/appbar/AppBar.tsx index b7308aa..820b479 100644 --- a/client/src/components/appbar/AppBar.tsx +++ b/client/src/components/appbar/AppBar.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { AppBar as MuiAppBar, Box, Tab as MuiTab, Tabs, IconButton } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; import AddIcon from '@material-ui/icons/Add'; @@ -61,7 +61,12 @@ export default function AppBar(props: IProps) { error - props.setSelectedTab(v)}> + props.setSelectedTab(v)} + variant="scrollable" + scrollButtons="auto" + > {props.tabLabels.map((l: string, idx: number) => 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(false); + + const editButton = + onChangeEditingValue(changedValue || defaultValue)} + > + + + + + const discardChangesButton = + { + onChangeChangedValue(null); + onChangeEditingValue(null); + }} + > + + + + + if (editing) { + return + onChangeEditingValue(e.target.value)} + /> + { + onChangeChangedValue(editingValue === defaultValue ? null : editingValue); + onChangeEditingValue(null); + }} + > + + } else if (changedValue) { + return setHovering(true)} + onMouseLeave={() => setHovering(false)} + display="flex" + alignItems="center" + > + {defaultValue}→ + {changedValue} + {editButton} + {discardChangesButton} + + } + + return setHovering(true)} + onMouseLeave={() => setHovering(false)} + display="flex" + alignItems="center" + >{defaultValue}{editButton}; +} \ No newline at end of file diff --git a/client/src/components/common/StoreLinkIcon.tsx b/client/src/components/common/StoreLinkIcon.tsx new file mode 100644 index 0000000..7359ab0 --- /dev/null +++ b/client/src/components/common/StoreLinkIcon.tsx @@ -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 ; + default: + throw new Error("Unknown external store: " + whichStore) + } +} \ No newline at end of file diff --git a/client/src/components/common/SubmitChangesButton.tsx b/client/src/components/common/SubmitChangesButton.tsx new file mode 100644 index 0000000..51ad1c5 --- /dev/null +++ b/client/src/components/common/SubmitChangesButton.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Box, Button } from '@material-ui/core'; + +export default function SubmitChangesButton(props: any) { + return + + +} \ No newline at end of file diff --git a/client/src/components/tables/ResultsTable.tsx b/client/src/components/tables/ResultsTable.tsx index 154bdaa..cce223f 100644 --- a/client/src/components/tables/ResultsTable.tsx +++ b/client/src/components/tables/ResultsTable.tsx @@ -7,6 +7,7 @@ import PersonIcon from '@material-ui/icons/Person'; import AlbumIcon from '@material-ui/icons/Album'; import AudiotrackIcon from '@material-ui/icons/Audiotrack'; import LocalOfferIcon from '@material-ui/icons/LocalOffer'; +import { songGetters } from '../../lib/songGetters'; export interface SongGetters { getTitle: (song: any) => string, @@ -25,13 +26,18 @@ export interface IProps { mainDispatch: (action: any) => void, } -export function SongTable(props: IProps) { - const useTableStyles = makeStyles({ +export default function SongTable(props: IProps) { + const classes = makeStyles({ + button: { + textTransform: "none", + fontWeight: 400, + paddingLeft: '0', + textAlign: 'left', + }, table: { minWidth: 650, }, - }); - const classes = useTableStyles(); + })(); return ( @@ -66,6 +72,8 @@ export function SongTable(props: IProps) { tabLabel: <>{mainArtistName}, artistId: mainArtistId, metadata: null, + songGetters: songGetters, + songsByArtist: null, }, tabReducer: newWindowReducer[WindowType.Artist], tabType: WindowType.Artist, @@ -79,6 +87,8 @@ export function SongTable(props: IProps) { tabLabel: <>{mainAlbumName}, albumId: mainAlbumId, metadata: null, + songGetters: songGetters, + songsOnAlbum: null, }, tabReducer: newWindowReducer[WindowType.Album], tabType: WindowType.Album, @@ -105,6 +115,8 @@ export function SongTable(props: IProps) { tabLabel: <>{name}, tagId: id, metadata: null, + songGetters: songGetters, + songsWithTag: null, }, tabReducer: newWindowReducer[WindowType.Tag], tabType: WindowType.Tag, @@ -118,20 +130,12 @@ export function SongTable(props: IProps) { return onClickTag(tagIds[i][tagIds[i].length-1], fullTag)} + onClick={() => onClickTag(tagIds[i][tagIds[i].length - 1], fullTag)} /> }); const TextCell = (props: any) => { - const classes = makeStyles({ - button: { - textTransform: "none", - fontWeight: 400, - paddingLeft: '0', - textAlign: 'left', - } - })(); return