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) {
- 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..a6a794b
--- /dev/null
+++ b/client/src/components/common/SubmitChangesButton.tsx
@@ -0,0 +1,12 @@
+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..978a123 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,7 +26,7 @@ export interface IProps {
mainDispatch: (action: any) => void,
}
-export function SongTable(props: IProps) {
+export default function SongTable(props: IProps) {
const useTableStyles = makeStyles({
table: {
minWidth: 650,
@@ -66,6 +67,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 +82,8 @@ export function SongTable(props: IProps) {
tabLabel: <>{mainAlbumName}>,
albumId: mainAlbumId,
metadata: null,
+ songGetters: songGetters,
+ songsOnAlbum: null,
},
tabReducer: newWindowReducer[WindowType.Album],
tabType: WindowType.Album,
@@ -118,7 +123,7 @@ export function SongTable(props: IProps) {
return
onClickTag(tagIds[i][tagIds[i].length-1], fullTag)}
+ onClick={() => onClickTag(tagIds[i][tagIds[i].length - 1], fullTag)}
/>
});
diff --git a/client/src/components/windows/AlbumWindow.tsx b/client/src/components/windows/AlbumWindow.tsx
index 2ad17a8..582b7bc 100644
--- a/client/src/components/windows/AlbumWindow.tsx
+++ b/client/src/components/windows/AlbumWindow.tsx
@@ -1,26 +1,39 @@
-import React, { useEffect } from 'react';
-import { Box, Typography } from '@material-ui/core';
-import AlbumIcon from '@material-ui/icons/Album';
+import React, { useEffect, useState } from 'react';
+import { Box, Typography, IconButton, Button, CircularProgress } from '@material-ui/core';
+import PersonIcon from '@material-ui/icons/Person';
import * as serverApi from '../../api';
import { WindowState } from './Windows';
+import StoreLinkIcon, { whichStore } from '../common/StoreLinkIcon';
+import EditableText from '../common/EditableText';
+import SubmitChangesButton from '../common/SubmitChangesButton';
+import SongTable, { SongGetters } from '../tables/ResultsTable';
+var _ = require('lodash');
-export interface AlbumMetadata {
- name: string,
-}
+export type AlbumMetadata = serverApi.AlbumDetails;
+export type AlbumMetadataChanges = serverApi.ModifyAlbumRequest;
export interface AlbumWindowState extends WindowState {
albumId: number,
metadata: AlbumMetadata | null,
+ pendingChanges: AlbumMetadataChanges | null,
+ songsOnAlbum: any[] | null,
+ songGetters: SongGetters,
}
export enum AlbumWindowStateActions {
SetMetadata = "SetMetadata",
+ SetPendingChanges = "SetPendingChanges",
+ SetSongs = "SetSongs",
}
export function AlbumWindowReducer(state: AlbumWindowState, action: any) {
switch (action.type) {
case AlbumWindowStateActions.SetMetadata:
return { ...state, metadata: action.value }
+ case AlbumWindowStateActions.SetPendingChanges:
+ return { ...state, pendingChanges: action.value }
+ case AlbumWindowStateActions.SetSongs:
+ return { ...state, songsOnAlbum: action.value }
default:
throw new Error("Unimplemented AlbumWindow state update.")
}
@@ -63,25 +76,96 @@ export async function getAlbumMetadata(id: number) {
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts)
let json: any = await response.json();
let album = json.albums[0];
- return {
- name: album.name
- }
+ return album;
})();
}
export default function AlbumWindow(props: IProps) {
let metadata = props.state.metadata;
+ let pendingChanges = props.state.pendingChanges;
+ // Effect to get the album's metadata.
useEffect(() => {
getAlbumMetadata(props.state.albumId)
.then((m: AlbumMetadata) => {
- console.log("metadata", m);
props.dispatch({
type: AlbumWindowStateActions.SetMetadata,
value: m
});
})
- }, [props.state.metadata?.name]);
+ }, [metadata?.name]);
+
+ // Effect to get the album's songs.
+ useEffect(() => {
+ if(props.state.songsOnAlbum) { return; }
+
+ var q: serverApi.QueryRequest = {
+ query: {
+ prop: serverApi.QueryElemProperty.albumId,
+ propOperator: serverApi.QueryFilterOp.Eq,
+ propOperand: props.state.albumId,
+ },
+ offsetsLimits: {
+ songOffset: 0,
+ songLimit: 100,
+ },
+ ordering: {
+ orderBy: {
+ type: serverApi.OrderByType.Name,
+ },
+ ascending: true,
+ },
+ };
+
+ const requestOpts = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(q),
+ };
+
+ (async () => {
+ const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts)
+ let json: any = await response.json();
+ props.dispatch({
+ type: AlbumWindowStateActions.SetSongs,
+ value: json.songs,
+ });
+ })();
+ }, [props.state.songsOnAlbum]);
+
+ const [editingName, setEditingName] = useState(null);
+ const name = setEditingName(v)}
+ onChangeChangedValue={(v: string | null) => {
+ let newVal: any = { ...pendingChanges };
+ if (v) { newVal.name = v }
+ else { delete newVal.name }
+ props.dispatch({
+ type: AlbumWindowStateActions.SetPendingChanges,
+ value: newVal,
+ })
+ }}
+ />
+
+ const storeLinks = metadata?.storeLinks && metadata?.storeLinks.map((link: string) => {
+ const store = whichStore(link);
+ return store &&
+
+
+
+ });
+
+ const maybeSubmitButton = pendingChanges && Object.keys(pendingChanges).length > 0 &&
+
return
-
+
+
+
+ {metadata &&
+
+ {name}
+
+
+
+ {storeLinks}
+
+
+ }
+
+
+ {maybeSubmitButton}
- {metadata && {metadata.name}}
+
+ Songs in this album in your library:
+
+ {props.state.songsOnAlbum && }
+ {!props.state.songsOnAlbum && }
}
\ No newline at end of file
diff --git a/client/src/components/windows/ArtistWindow.tsx b/client/src/components/windows/ArtistWindow.tsx
index d3453eb..67dfb70 100644
--- a/client/src/components/windows/ArtistWindow.tsx
+++ b/client/src/components/windows/ArtistWindow.tsx
@@ -1,26 +1,39 @@
-import React, { useEffect } from 'react';
-import { Box, Typography } from '@material-ui/core';
+import React, { useEffect, useState } from 'react';
+import { Box, Typography, IconButton, Button, CircularProgress } from '@material-ui/core';
import PersonIcon from '@material-ui/icons/Person';
import * as serverApi from '../../api';
import { WindowState } from './Windows';
+import StoreLinkIcon, { whichStore } from '../common/StoreLinkIcon';
+import EditableText from '../common/EditableText';
+import SubmitChangesButton from '../common/SubmitChangesButton';
+import SongTable, { SongGetters } from '../tables/ResultsTable';
+var _ = require('lodash');
-export interface ArtistMetadata {
- name: string,
-}
+export type ArtistMetadata = serverApi.ArtistDetails;
+export type ArtistMetadataChanges = serverApi.ModifyArtistRequest;
export interface ArtistWindowState extends WindowState {
artistId: number,
metadata: ArtistMetadata | null,
+ pendingChanges: ArtistMetadataChanges | null,
+ songsByArtist: any[] | null,
+ songGetters: SongGetters,
}
export enum ArtistWindowStateActions {
SetMetadata = "SetMetadata",
+ SetPendingChanges = "SetPendingChanges",
+ SetSongs = "SetSongs",
}
export function ArtistWindowReducer(state: ArtistWindowState, action: any) {
switch (action.type) {
case ArtistWindowStateActions.SetMetadata:
return { ...state, metadata: action.value }
+ case ArtistWindowStateActions.SetPendingChanges:
+ return { ...state, pendingChanges: action.value }
+ case ArtistWindowStateActions.SetSongs:
+ return { ...state, songsByArtist: action.value }
default:
throw new Error("Unimplemented ArtistWindow state update.")
}
@@ -63,25 +76,96 @@ export async function getArtistMetadata(id: number) {
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts)
let json: any = await response.json();
let artist = json.artists[0];
- return {
- name: artist.name
- }
+ return artist;
})();
}
export default function ArtistWindow(props: IProps) {
let metadata = props.state.metadata;
+ let pendingChanges = props.state.pendingChanges;
+ // Effect to get the artist's metadata.
useEffect(() => {
getArtistMetadata(props.state.artistId)
.then((m: ArtistMetadata) => {
- console.log("metadata", m);
props.dispatch({
type: ArtistWindowStateActions.SetMetadata,
value: m
});
})
- }, [props.state.metadata?.name]);
+ }, [metadata?.name]);
+
+ // Effect to get the artist's songs.
+ useEffect(() => {
+ if(props.state.songsByArtist) { return; }
+
+ var q: serverApi.QueryRequest = {
+ query: {
+ prop: serverApi.QueryElemProperty.artistId,
+ propOperator: serverApi.QueryFilterOp.Eq,
+ propOperand: props.state.artistId,
+ },
+ offsetsLimits: {
+ songOffset: 0,
+ songLimit: 100,
+ },
+ ordering: {
+ orderBy: {
+ type: serverApi.OrderByType.Name,
+ },
+ ascending: true,
+ },
+ };
+
+ const requestOpts = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(q),
+ };
+
+ (async () => {
+ const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts)
+ let json: any = await response.json();
+ props.dispatch({
+ type: ArtistWindowStateActions.SetSongs,
+ value: json.songs,
+ });
+ })();
+ }, [props.state.songsByArtist]);
+
+ const [editingName, setEditingName] = useState(null);
+ const name = setEditingName(v)}
+ onChangeChangedValue={(v: string | null) => {
+ let newVal: any = { ...pendingChanges };
+ if (v) { newVal.name = v }
+ else { delete newVal.name }
+ props.dispatch({
+ type: ArtistWindowStateActions.SetPendingChanges,
+ value: newVal,
+ })
+ }}
+ />
+
+ const storeLinks = metadata?.storeLinks && metadata?.storeLinks.map((link: string) => {
+ const store = whichStore(link);
+ return store &&
+
+
+
+ });
+
+ const maybeSubmitButton = pendingChanges && Object.keys(pendingChanges).length > 0 &&
+
return
-
+
+
+
+ {metadata &&
+
+ {name}
+
+
+
+ {storeLinks}
+
+
+ }
+
+
+ {maybeSubmitButton}
- {metadata && {metadata.name}}
+
+ Songs by this artist in your library:
+
+ {props.state.songsByArtist && }
+ {!props.state.songsByArtist && }
}
\ No newline at end of file
diff --git a/client/src/components/windows/QueryWindow.tsx b/client/src/components/windows/QueryWindow.tsx
index fa7507e..e35d233 100644
--- a/client/src/components/windows/QueryWindow.tsx
+++ b/client/src/components/windows/QueryWindow.tsx
@@ -3,8 +3,8 @@ import { createMuiTheme, Box, LinearProgress } from '@material-ui/core';
import { QueryElem, toApiQuery } from '../../lib/query/Query';
import QueryBuilder from '../querybuilder/QueryBuilder';
import * as serverApi from '../../api';
-import { SongTable } from '../tables/ResultsTable';
-import stringifyList from '../../lib/stringifyList';
+import SongTable from '../tables/ResultsTable';
+import { songGetters } from '../../lib/songGetters';
import { getArtists, getSongTitles, getAlbums, getTags } from '../../lib/query/Getters';
import { grey } from '@material-ui/core/colors';
import { WindowState } from './Windows';
@@ -72,35 +72,6 @@ export default function QueryWindow(props: IProps) {
const loading = query && (!resultsFor || !_.isEqual(resultsFor.for, query));
const showResults = (query && resultsFor && query == resultsFor.for) ? resultsFor.results : [];
- 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));
- },
- }
-
const doQuery = async (_query: QueryElem) => {
var q: serverApi.QueryRequest = {
query: toApiQuery(_query),
diff --git a/client/src/components/windows/SongWindow.tsx b/client/src/components/windows/SongWindow.tsx
index b937b8f..6c3aa2f 100644
--- a/client/src/components/windows/SongWindow.tsx
+++ b/client/src/components/windows/SongWindow.tsx
@@ -1,26 +1,36 @@
-import React, { useEffect } from 'react';
-import { Box, Typography } from '@material-ui/core';
+import React, { useEffect, useState } from 'react';
+import { Box, Typography, IconButton, Button } from '@material-ui/core';
import AudiotrackIcon from '@material-ui/icons/Audiotrack';
+import PersonIcon from '@material-ui/icons/Person';
+import AlbumIcon from '@material-ui/icons/Album';
import * as serverApi from '../../api';
import { WindowState } from './Windows';
+import { ArtistMetadata } from './ArtistWindow';
+import { AlbumMetadata } from './AlbumWindow';
+import StoreLinkIcon, { whichStore } from '../common/StoreLinkIcon';
+import EditableText from '../common/EditableText';
+import SubmitChangesButton from '../common/SubmitChangesButton';
-export interface SongMetadata {
- title: string,
-}
+export type SongMetadata = serverApi.SongDetails;
+export type SongMetadataChanges = serverApi.ModifySongRequest;
export interface SongWindowState extends WindowState {
songId: number,
metadata: SongMetadata | null,
+ pendingChanges: SongMetadataChanges | null,
}
export enum SongWindowStateActions {
SetMetadata = "SetMetadata",
+ SetPendingChanges = "SetPendingChanges",
}
export function SongWindowReducer(state: SongWindowState, action: any) {
switch (action.type) {
case SongWindowStateActions.SetMetadata:
return { ...state, metadata: action.value }
+ case SongWindowStateActions.SetPendingChanges:
+ return { ...state, pendingChanges: action.value }
default:
throw new Error("Unimplemented SongWindow state update.")
}
@@ -63,25 +73,69 @@ export async function getSongMetadata(id: number) {
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts)
let json: any = await response.json();
let song = json.songs[0];
- return {
- title: song.title
- }
+ return song;
})();
}
export default function SongWindow(props: IProps) {
let metadata = props.state.metadata;
+ let pendingChanges = props.state.pendingChanges;
useEffect(() => {
getSongMetadata(props.state.songId)
.then((m: SongMetadata) => {
- console.log("metadata", m);
props.dispatch({
type: SongWindowStateActions.SetMetadata,
value: m
});
})
- }, [props.state.metadata?.title]);
+ }, [metadata?.title]);
+
+ const [editingTitle, setEditingTitle] = useState(null);
+ const title = setEditingTitle(v)}
+ onChangeChangedValue={(v: string | null) => {
+ let newVal: any = { ...pendingChanges };
+ if(v) { newVal.title = v }
+ else { delete newVal.title }
+ props.dispatch({
+ type: SongWindowStateActions.SetPendingChanges,
+ value: newVal,
+ })
+ }}
+ />
+
+ const artists = metadata?.artists && metadata?.artists.map((artist: ArtistMetadata) => {
+ return
+ {artist.name}
+
+ });
+
+ const albums = metadata?.albums && metadata?.albums.map((album: AlbumMetadata) => {
+ return
+ {album.name}
+
+ });
+
+ const storeLinks = metadata?.storeLinks && metadata?.storeLinks.map((link: string) => {
+ const store = whichStore(link);
+ return store &&
+
+
+
+ });
+
+ const maybeSubmitButton = pendingChanges && Object.keys(pendingChanges).length > 0 &&
+
return
-
+
+
+
+ {metadata &&
+
+ {title}
+
+
+
+
+
+ {artists}
+
+
+
+
+
+
+
+ {albums}
+
+
+
+
+
+ {storeLinks}
+
+
+ }
- {metadata && {metadata.title}}
+ {maybeSubmitButton}
}
\ No newline at end of file
diff --git a/client/src/components/windows/TagWindow.tsx b/client/src/components/windows/TagWindow.tsx
index 49c7f50..f504312 100644
--- a/client/src/components/windows/TagWindow.tsx
+++ b/client/src/components/windows/TagWindow.tsx
@@ -4,9 +4,7 @@ import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import * as serverApi from '../../api';
import { WindowState } from './Windows';
-export interface TagMetadata {
- name: string,
-}
+export type TagMetadata = serverApi.TagDetails;
export interface TagWindowState extends WindowState {
tagId: number,
@@ -63,9 +61,7 @@ export async function getTagMetadata(id: number) {
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts)
let json: any = await response.json();
let tag = json.tags[0];
- return {
- name: tag.name
- }
+ return tag;
})();
}
diff --git a/client/src/components/windows/Windows.tsx b/client/src/components/windows/Windows.tsx
index 77b79f3..7460ae0 100644
--- a/client/src/components/windows/Windows.tsx
+++ b/client/src/components/windows/Windows.tsx
@@ -9,6 +9,7 @@ import AudiotrackIcon from '@material-ui/icons/Audiotrack';
import { SongWindowReducer } from './SongWindow';
import { AlbumWindowReducer } from './AlbumWindow';
import { TagWindowReducer } from './TagWindow';
+import { songGetters } from '../../lib/songGetters';
export enum WindowType {
Query = "Query",
@@ -41,30 +42,38 @@ export const newWindowState = {
},
[WindowType.Artist]: () => {
return {
- tabLabel: <>Artist>,
+ tabLabel: <>Artist 1>,
artistId: 1,
metadata: null,
+ pendingChanges: null,
+ songGetters: songGetters,
+ songsByArtist: null,
}
},
[WindowType.Album]: () => {
return {
- tabLabel: <>Album>,
+ tabLabel: <>Album 1>,
albumId: 1,
metadata: null,
+ pendingChanges: null,
+ songGetters: songGetters,
+ songsOnAlbum: null,
}
},
[WindowType.Song]: () => {
return {
- tabLabel: <>Song>,
+ tabLabel: <>Song 1>,
songId: 1,
metadata: null,
+ pendingChanges: null,
}
},
[WindowType.Tag]: () => {
return {
- tabLabel: <>Tag>,
+ tabLabel: <>Tag 1>,
tagId: 1,
metadata: null,
+ pendingChanges: null,
}
},
}
\ No newline at end of file
diff --git a/client/src/lib/songGetters.tsx b/client/src/lib/songGetters.tsx
new file mode 100644
index 0000000..4f75cc9
--- /dev/null
+++ b/client/src/lib/songGetters.tsx
@@ -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));
+ },
+}
\ No newline at end of file