diff --git a/client/src/App.tsx b/client/src/App.tsx
index 6f9ae76..075a107 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -3,25 +3,13 @@ import React from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
-import {
- HashRouter as Router,
- Switch,
- Route
-} from "react-router-dom";
import MainWindow from './components/MainWindow';
function App() {
return (
-
-
-
-
-
-
-
-
-
-
+
+
+
);
}
diff --git a/client/src/components/MainWindow.tsx b/client/src/components/MainWindow.tsx
index d600867..2839106 100644
--- a/client/src/components/MainWindow.tsx
+++ b/client/src/components/MainWindow.tsx
@@ -1,15 +1,15 @@
-import React, { useReducer, Reducer } from 'react';
+import React, { useReducer, Reducer, useContext } 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/query/QueryWindow';
-import { NewTabProps } from './appbar/AddTabMenu';
+import AppBar, { AppBarTab } from './appbar/AppBar';
+import QueryWindow, { QueryWindowReducer } from './windows/query/QueryWindow';
import { newWindowState, newWindowReducer, WindowType } from './windows/Windows';
import ArtistWindow from './windows/artist/ArtistWindow';
import AlbumWindow from './windows/album/AlbumWindow';
import TagWindow from './windows/tag/TagWindow';
import SongWindow from './windows/song/SongWindow';
import ManageTagsWindow from './windows/manage_tags/ManageTagsWindow';
+import { BrowserRouter, Switch, Route, useParams, Redirect } from 'react-router-dom';
var _ = require('lodash');
const darkTheme = createMuiTheme({
@@ -21,150 +21,39 @@ const darkTheme = createMuiTheme({
},
});
-export interface MainWindowState {
- tabStates: any[],
- tabReducers: Reducer[],
- tabTypes: WindowType[],
- activeTab: number,
-}
-
-export enum MainWindowStateActions {
- SetActiveTab = "setActiveTab",
- DispatchToTab = "dispatchToTab",
- CloseTab = "closeTab",
- AddTab = "addTab",
-}
-
-export function MainWindowReducer(state: MainWindowState, action: any) {
- switch (action.type) {
- case MainWindowStateActions.SetActiveTab:
- return { ...state, activeTab: action.value }
- case MainWindowStateActions.CloseTab:
- const newSize = state.tabStates.length - 1;
- return {
- ...state,
- tabStates: state.tabStates.filter((i: any, idx: number) => idx != action.idx),
- tabReducers: state.tabReducers.filter((i: any, idx: number) => idx != action.idx),
- tabTypes: state.tabTypes.filter((i: any, idx: number) => idx != action.idx),
- activeTab: state.activeTab >= (newSize - 1) ? (newSize - 1) : state.activeTab,
- }
- case MainWindowStateActions.AddTab:
- return {
- ...state,
- tabStates: [...state.tabStates, action.tabState],
- tabReducers: [...state.tabReducers, action.tabReducer],
- tabTypes: [...state.tabTypes, action.tabType],
- }
- case MainWindowStateActions.DispatchToTab:
- return {
- ...state,
- tabStates: state.tabStates.map((item: any, i: number) => {
- return i === action.idx ?
- state.tabReducers[i](item, action.tabAction) :
- item;
- })
- }
- default:
- throw new Error("Unimplemented MainWindow state update.")
- }
-}
-
export default function MainWindow(props: any) {
- const [state, dispatch] = useReducer(MainWindowReducer, {
- tabStates: [
- newWindowState[WindowType.Query](),
- newWindowState[WindowType.Song](),
- newWindowState[WindowType.Album](),
- newWindowState[WindowType.Artist](),
- newWindowState[WindowType.Tag](),
- newWindowState[WindowType.ManageTags](),
- ],
- tabReducers: [
- newWindowReducer[WindowType.Query],
- newWindowReducer[WindowType.Song],
- newWindowReducer[WindowType.Album],
- newWindowReducer[WindowType.Artist],
- newWindowReducer[WindowType.Tag],
- newWindowReducer[WindowType.ManageTags],
- ],
- tabTypes: [
- WindowType.Query,
- WindowType.Song,
- WindowType.Album,
- WindowType.Artist,
- WindowType.Tag,
- WindowType.ManageTags,
- ],
- activeTab: 0
- })
-
- const windows = state.tabStates.map((tabState: any, i: number) => {
- const tabDispatch = (action: any) => {
- dispatch({
- type: MainWindowStateActions.DispatchToTab,
- tabAction: action,
- idx: i
- });
- }
-
- switch (state.tabTypes[i]) {
- case WindowType.Query:
- return
- case WindowType.Artist:
- return
- case WindowType.Album:
- return
- case WindowType.Tag:
- return
- case WindowType.Song:
- return
- case WindowType.ManageTags:
- return
- default:
- throw new Error("Unimplemented window type");
- }
- });
-
return
- s.tabLabel)}
- selectedTab={state.activeTab}
- setSelectedTab={(t: number) => dispatch({ type: MainWindowStateActions.SetActiveTab, value: t })}
- onCloseTab={(t: number) => dispatch({ type: MainWindowStateActions.CloseTab, idx: t })}
- onAddTab={(w: NewTabProps) => {
- dispatch({
- type: MainWindowStateActions.AddTab,
- tabState: newWindowState[w.windowType](),
- tabReducer: newWindowReducer[w.windowType],
- tabType: w.windowType,
- })
- }}
- />
- {windows[state.activeTab]}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
\ No newline at end of file
diff --git a/client/src/components/appbar/AddTabMenu.tsx b/client/src/components/appbar/AddTabMenu.tsx
deleted file mode 100644
index a9e0755..0000000
--- a/client/src/components/appbar/AddTabMenu.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-import { WindowType } from '../windows/Windows';
-import { Menu, MenuItem } from '@material-ui/core';
-
-export interface NewTabProps {
- windowType: WindowType,
-}
-
-export interface IProps {
- anchorEl: null | HTMLElement,
- onClose: () => void,
- onCreateTab: (q: NewTabProps) => void,
-}
-
-export default function AddTabMenu(props: IProps) {
- return
-}
\ No newline at end of file
diff --git a/client/src/components/appbar/AppBar.tsx b/client/src/components/appbar/AppBar.tsx
index 820b479..28809f2 100644
--- a/client/src/components/appbar/AppBar.tsx
+++ b/client/src/components/appbar/AppBar.tsx
@@ -1,85 +1,52 @@
import React from 'react';
-import { AppBar as MuiAppBar, Box, Tab as MuiTab, Tabs, IconButton } from '@material-ui/core';
+import { AppBar as MuiAppBar, Box, Tab as MuiTab, Tabs, IconButton, Typography } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
-import AddIcon from '@material-ui/icons/Add';
-import AddTabMenu, { NewTabProps } from './AddTabMenu';
-
-export interface IProps {
- tabLabels: string[],
- selectedTab: number,
- setSelectedTab: (n: number) => void,
- onCloseTab: (idx: number) => void,
- onAddTab: (w: NewTabProps) => void,
-}
-
-export interface TabProps {
- onClose: () => void,
+import SearchIcon from '@material-ui/icons/Search';
+import LocalOfferIcon from '@material-ui/icons/LocalOffer';
+import { Link, useHistory } from 'react-router-dom';
+import { WindowType } from '../windows/Windows';
+
+export enum AppBarTab {
+ Query = 0,
+ Tags,
}
-export function Tab(props: any) {
- const { onClose, label, ...restProps } = props;
-
- const labelElem =
- {label}
-
-
-
-
-
- ;
-
- return
+export const appBarTabProps: Record = {
+ [AppBarTab.Query]: {
+ label: Query,
+ path: "/query",
+ },
+ [AppBarTab.Tags]: {
+ label: Tags,
+ path: "/tags",
+ },
}
-export default function AppBar(props: IProps) {
- const [addMenuAnchorEl, setAddMenuAnchorEl] = React.useState(null);
-
- const onOpenAddMenu = (event: any) => {
- setAddMenuAnchorEl(event.currentTarget);
- };
- const onCloseAddMenu = () => {
- setAddMenuAnchorEl(null);
- };
- const onAddTab = (w: NewTabProps) => {
- props.onAddTab(w);
- };
+export default function AppBar(props: {
+ selectedTab: AppBarTab | null
+}) {
+ const history = useHistory();
return <>
-
-
-
+
+
+
+
+
props.setSelectedTab(v)}
+ onChange={(e: any, val: AppBarTab) => history.push(appBarTabProps[val].path)}
variant="scrollable"
scrollButtons="auto"
>
- {props.tabLabels.map((l: string, idx: number) => props.onCloseTab(idx)}
/>)}
-
-
>
}
\ No newline at end of file
diff --git a/client/src/components/tables/ResultsTable.tsx b/client/src/components/tables/ResultsTable.tsx
index cce223f..5fee3d1 100644
--- a/client/src/components/tables/ResultsTable.tsx
+++ b/client/src/components/tables/ResultsTable.tsx
@@ -1,13 +1,7 @@
import React from 'react';
import { TableContainer, Table, TableHead, TableRow, TableCell, Paper, makeStyles, TableBody, Chip, Box, Button } from '@material-ui/core';
import stringifyList from '../../lib/stringifyList';
-import { MainWindowStateActions } from '../MainWindow';
-import { newWindowReducer, WindowType } from '../windows/Windows';
-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';
+import { useHistory } from 'react-router';
export interface SongGetters {
getTitle: (song: any) => string,
@@ -20,13 +14,12 @@ export interface SongGetters {
getTagIds: (song: any) => number[][], // Each tag is represented as a series of ids.
}
-export interface IProps {
+export default function SongTable(props: {
songs: any[],
songGetters: SongGetters,
- mainDispatch: (action: any) => void,
-}
+}) {
+ const history = useHistory();
-export default function SongTable(props: IProps) {
const classes = makeStyles({
button: {
textTransform: "none",
@@ -66,61 +59,19 @@ export default function SongTable(props: IProps) {
const tagIds = props.songGetters.getTagIds(song);
const onClickArtist = () => {
- props.mainDispatch({
- type: MainWindowStateActions.AddTab,
- tabState: {
- tabLabel: <>{mainArtistName}>,
- artistId: mainArtistId,
- metadata: null,
- songGetters: songGetters,
- songsByArtist: null,
- },
- tabReducer: newWindowReducer[WindowType.Artist],
- tabType: WindowType.Artist,
- })
+ history.push('/artist/' + mainArtistId);
}
const onClickAlbum = () => {
- props.mainDispatch({
- type: MainWindowStateActions.AddTab,
- tabState: {
- tabLabel: <>{mainAlbumName}>,
- albumId: mainAlbumId,
- metadata: null,
- songGetters: songGetters,
- songsOnAlbum: null,
- },
- tabReducer: newWindowReducer[WindowType.Album],
- tabType: WindowType.Album,
- })
+ history.push('/album/' + mainAlbumId);
}
const onClickSong = () => {
- props.mainDispatch({
- type: MainWindowStateActions.AddTab,
- tabState: {
- tabLabel: <>{title}>,
- songId: songId,
- metadata: null,
- },
- tabReducer: newWindowReducer[WindowType.Song],
- tabType: WindowType.Song,
- })
+ history.push('/song/' + songId);
}
const onClickTag = (id: number, name: string) => {
- props.mainDispatch({
- type: MainWindowStateActions.AddTab,
- tabState: {
- tabLabel: <>{name}>,
- tagId: id,
- metadata: null,
- songGetters: songGetters,
- songsWithTag: null,
- },
- tabReducer: newWindowReducer[WindowType.Tag],
- tabType: WindowType.Tag,
- })
+ history.push('/tag/' + id);
}
const tags = props.songGetters.getTagNames(song).map((tag: string[], i: number) => {
diff --git a/client/src/components/windows/Windows.tsx b/client/src/components/windows/Windows.tsx
index 1411e33..edd6768 100644
--- a/client/src/components/windows/Windows.tsx
+++ b/client/src/components/windows/Windows.tsx
@@ -1,17 +1,17 @@
-import React from 'react';
-import { QueryWindowReducer } from "./query/QueryWindow";
-import { ArtistWindowReducer } from "./artist/ArtistWindow";
+import React, { useReducer } from 'react';
+import QueryWindow, { QueryWindowReducer } from "./query/QueryWindow";
+import ArtistWindow, { ArtistWindowReducer } from "./artist/ArtistWindow";
import SearchIcon from '@material-ui/icons/Search';
import PersonIcon from '@material-ui/icons/Person';
import AlbumIcon from '@material-ui/icons/Album';
import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import AudiotrackIcon from '@material-ui/icons/Audiotrack';
import LoyaltyIcon from '@material-ui/icons/Loyalty';
-import { SongWindowReducer } from './song/SongWindow';
-import { AlbumWindowReducer } from './album/AlbumWindow';
-import { TagWindowReducer } from './tag/TagWindow';
+import SongWindow, { SongWindowReducer } from './song/SongWindow';
+import AlbumWindow, { AlbumWindowReducer } from './album/AlbumWindow';
+import TagWindow, { TagWindowReducer } from './tag/TagWindow';
import { songGetters } from '../../lib/songGetters';
-import { ManageTagsWindowReducer } from './manage_tags/ManageTagsWindow';
+import ManageTagsWindow, { ManageTagsWindowReducer } from './manage_tags/ManageTagsWindow';
export enum WindowType {
Query = "Query",
@@ -22,9 +22,7 @@ export enum WindowType {
ManageTags = "ManageTags",
}
-export interface WindowState {
- tabLabel: string,
-}
+export interface WindowState { }
export const newWindowReducer = {
[WindowType.Query]: QueryWindowReducer,
@@ -38,7 +36,6 @@ export const newWindowReducer = {
export const newWindowState = {
[WindowType.Query]: () => {
return {
- tabLabel: <>Query>,
editingQuery: false,
query: null,
resultsForQuery: null,
@@ -46,8 +43,7 @@ export const newWindowState = {
},
[WindowType.Artist]: () => {
return {
- tabLabel: <>Artist 1>,
- artistId: 1,
+ id: 1,
metadata: null,
pendingChanges: null,
songGetters: songGetters,
@@ -56,8 +52,7 @@ export const newWindowState = {
},
[WindowType.Album]: () => {
return {
- tabLabel: <>Album 1>,
- albumId: 1,
+ id: 1,
metadata: null,
pendingChanges: null,
songGetters: songGetters,
@@ -66,16 +61,14 @@ export const newWindowState = {
},
[WindowType.Song]: () => {
return {
- tabLabel: <>Song 1>,
- songId: 1,
+ id: 1,
metadata: null,
pendingChanges: null,
}
},
[WindowType.Tag]: () => {
return {
- tabLabel: <>Tag 1>,
- tagId: 1,
+ id: 1,
metadata: null,
pendingChanges: null,
songGetters: songGetters,
@@ -84,8 +77,8 @@ export const newWindowState = {
},
[WindowType.ManageTags]: () => {
return {
- tabLabel: <>Manage Tags>,
fetchedTags: null,
+ alert: null,
pendingChanges: [],
}
}
diff --git a/client/src/components/windows/album/AlbumWindow.tsx b/client/src/components/windows/album/AlbumWindow.tsx
index d879bfc..07daa38 100644
--- a/client/src/components/windows/album/AlbumWindow.tsx
+++ b/client/src/components/windows/album/AlbumWindow.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useReducer } from 'react';
import { Box, Typography, IconButton, CircularProgress } from '@material-ui/core';
import AlbumIcon from '@material-ui/icons/Album';
import * as serverApi from '../../../api';
@@ -10,13 +10,15 @@ import SongTable, { SongGetters } from '../../tables/ResultsTable';
import { saveAlbumChanges } from '../../../lib/saveChanges';
import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import { queryAlbums, querySongs } from '../../../lib/backend/queries';
+import { songGetters } from '../../../lib/songGetters';
+import { useParams } from 'react-router';
var _ = require('lodash');
export type AlbumMetadata = serverApi.AlbumDetails;
export type AlbumMetadataChanges = serverApi.ModifyAlbumRequest;
export interface AlbumWindowState extends WindowState {
- albumId: number,
+ id: number,
metadata: AlbumMetadata | null,
pendingChanges: AlbumMetadataChanges | null,
songsOnAlbum: any[] | null,
@@ -45,12 +47,6 @@ export function AlbumWindowReducer(state: AlbumWindowState, action: any) {
}
}
-export interface IProps {
- state: AlbumWindowState,
- dispatch: (action: any) => void,
- mainDispatch: (action: any) => void,
-}
-
export async function getAlbumMetadata(id: number) {
return (await queryAlbums({
query: {
@@ -63,13 +59,29 @@ export async function getAlbumMetadata(id: number) {
}))[0];
}
-export default function AlbumWindow(props: IProps) {
+export default function AlbumWindow(props: {}) {
+ const { id } = useParams();
+ const [state, dispatch] = useReducer(AlbumWindowReducer, {
+ id: id,
+ metadata: null,
+ pendingChanges: null,
+ songGetters: songGetters,
+ songsOnAlbum: null,
+ });
+
+ return
+}
+
+export function AlbumWindowControlled(props: {
+ state: AlbumWindowState,
+ dispatch: (action: any) => void,
+}) {
let metadata = props.state.metadata;
let pendingChanges = props.state.pendingChanges;
// Effect to get the album's metadata.
useEffect(() => {
- getAlbumMetadata(props.state.albumId)
+ getAlbumMetadata(props.state.id)
.then((m: AlbumMetadata) => {
props.dispatch({
type: AlbumWindowStateActions.SetMetadata,
@@ -86,7 +98,7 @@ export default function AlbumWindow(props: IProps) {
const songs = await querySongs({
query: {
a: QueryLeafBy.AlbumId,
- b: props.state.albumId,
+ b: props.state.id,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
@@ -95,7 +107,7 @@ export default function AlbumWindow(props: IProps) {
props.dispatch({
type: AlbumWindowStateActions.SetSongs,
value: songs,
- });
+ });
})();
}, [props.state.songsOnAlbum]);
@@ -135,7 +147,7 @@ export default function AlbumWindow(props: IProps) {
{
setApplying(true);
- saveAlbumChanges(props.state.albumId, pendingChanges || {})
+ saveAlbumChanges(props.state.id, pendingChanges || {})
.then(() => {
setApplying(false);
props.dispatch({
@@ -185,7 +197,6 @@ export default function AlbumWindow(props: IProps) {
{props.state.songsOnAlbum && }
{!props.state.songsOnAlbum && }
diff --git a/client/src/components/windows/artist/ArtistWindow.tsx b/client/src/components/windows/artist/ArtistWindow.tsx
index fd76abb..ce28da5 100644
--- a/client/src/components/windows/artist/ArtistWindow.tsx
+++ b/client/src/components/windows/artist/ArtistWindow.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useReducer } from 'react';
import { Box, Typography, IconButton, Button, CircularProgress } from '@material-ui/core';
import PersonIcon from '@material-ui/icons/Person';
import * as serverApi from '../../../api';
@@ -10,13 +10,15 @@ import SongTable, { SongGetters } from '../../tables/ResultsTable';
import { saveArtistChanges } from '../../../lib/saveChanges';
import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query';
import { queryArtists, querySongs } from '../../../lib/backend/queries';
+import { songGetters } from '../../../lib/songGetters';
+import { useParams } from 'react-router';
var _ = require('lodash');
export type ArtistMetadata = serverApi.ArtistDetails;
export type ArtistMetadataChanges = serverApi.ModifyArtistRequest;
export interface ArtistWindowState extends WindowState {
- artistId: number,
+ id: number,
metadata: ArtistMetadata | null,
pendingChanges: ArtistMetadataChanges | null,
songsByArtist: any[] | null,
@@ -48,7 +50,6 @@ export function ArtistWindowReducer(state: ArtistWindowState, action: any) {
export interface IProps {
state: ArtistWindowState,
dispatch: (action: any) => void,
- mainDispatch: (action: any) => void,
}
export async function getArtistMetadata(id: number) {
@@ -63,13 +64,29 @@ export async function getArtistMetadata(id: number) {
}))[0];
}
-export default function ArtistWindow(props: IProps) {
+export default function ArtistWindow(props: {}) {
+ const { id } = useParams();
+ const [state, dispatch] = useReducer(ArtistWindowReducer, {
+ id: id,
+ metadata: null,
+ pendingChanges: null,
+ songGetters: songGetters,
+ songsByArtist: null,
+ });
+
+ return
+}
+
+export function ArtistWindowControlled(props: {
+ state: ArtistWindowState,
+ dispatch: (action: any) => void,
+}) {
let metadata = props.state.metadata;
let pendingChanges = props.state.pendingChanges;
// Effect to get the artist's metadata.
useEffect(() => {
- getArtistMetadata(props.state.artistId)
+ getArtistMetadata(props.state.id)
.then((m: ArtistMetadata) => {
props.dispatch({
type: ArtistWindowStateActions.SetMetadata,
@@ -86,7 +103,7 @@ export default function ArtistWindow(props: IProps) {
const songs = await querySongs({
query: {
a: QueryLeafBy.ArtistId,
- b: props.state.artistId,
+ b: props.state.id,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
@@ -95,7 +112,7 @@ export default function ArtistWindow(props: IProps) {
props.dispatch({
type: ArtistWindowStateActions.SetSongs,
value: songs,
- });
+ });
})();
}, [props.state.songsByArtist]);
@@ -135,7 +152,7 @@ export default function ArtistWindow(props: IProps) {
{
setApplying(true);
- saveArtistChanges(props.state.artistId, pendingChanges || {})
+ saveArtistChanges(props.state.id, pendingChanges || {})
.then(() => {
setApplying(false);
props.dispatch({
@@ -185,7 +202,6 @@ export default function ArtistWindow(props: IProps) {
{props.state.songsByArtist && }
{!props.state.songsByArtist && }
diff --git a/client/src/components/windows/manage_tags/ManageTagMenu.tsx b/client/src/components/windows/manage_tags/ManageTagMenu.tsx
index 5b5f9a1..376f340 100644
--- a/client/src/components/windows/manage_tags/ManageTagMenu.tsx
+++ b/client/src/components/windows/manage_tags/ManageTagMenu.tsx
@@ -35,7 +35,7 @@ export default function ManageTagMenu(props: {
onDelete: () => void,
onMove: (to: string | null) => void,
onMergeInto: (to: string) => void,
- onOpenInTab: () => void,
+ onOpenTag: () => void,
tag: any,
changedTags: any[], // Tags organized hierarchically with "children" fields
}) {
@@ -53,7 +53,7 @@ export default function ManageTagMenu(props: {