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..b028dfb 100644
--- a/client/src/components/MainWindow.tsx
+++ b/client/src/components/MainWindow.tsx
@@ -2,14 +2,15 @@ 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/query/QueryWindow';
+import QueryWindow, { QueryWindowReducer } from './windows/query/QueryWindow';
import { NewTabProps } from './appbar/AddTabMenu';
-import { newWindowState, newWindowReducer, WindowType } from './windows/Windows';
+import { newWindowState, newWindowReducer, WindowType, Window } 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 +22,56 @@ 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.")
- }
+function WindowContent(props: {
+ type: WindowType,
+}) {
+ const { id } = useParams();
+ return
}
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");
- }
- });
+ const [windowState, windowDispatch] = useReducer(
+ QueryWindowReducer,
+ {
+ editingQuery: false,
+ query: null,
+ resultsForQuery: null,
+ },
+ );
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]}
+
+ { }}
+ onCloseTab={(t: number) => { }}
+ onAddTab={(w: NewTabProps) => { }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
\ No newline at end of file
diff --git a/client/src/components/appbar/AppBar.tsx b/client/src/components/appbar/AppBar.tsx
index 820b479..bf0f252 100644
--- a/client/src/components/appbar/AppBar.tsx
+++ b/client/src/components/appbar/AppBar.tsx
@@ -3,6 +3,7 @@ import { AppBar as MuiAppBar, Box, Tab as MuiTab, Tabs, IconButton } from '@mate
import CloseIcon from '@material-ui/icons/Close';
import AddIcon from '@material-ui/icons/Add';
import AddTabMenu, { NewTabProps } from './AddTabMenu';
+import { Link } from 'react-router-dom';
export interface IProps {
tabLabels: string[],
@@ -58,9 +59,11 @@ export default function AppBar(props: IProps) {
return <>
-
-
-
+
+
+
+
+
props.setSelectedTab(v)}
diff --git a/client/src/components/tables/ResultsTable.tsx b/client/src/components/tables/ResultsTable.tsx
index cce223f..5952b95 100644
--- a/client/src/components/tables/ResultsTable.tsx
+++ b/client/src/components/tables/ResultsTable.tsx
@@ -1,13 +1,13 @@
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 { Redirect, useHistory } from 'react-router';
export interface SongGetters {
getTitle: (song: any) => string,
@@ -23,10 +23,11 @@ export interface SongGetters {
export interface IProps {
songs: any[],
songGetters: SongGetters,
- mainDispatch: (action: any) => void,
}
export default function SongTable(props: IProps) {
+ const history = useHistory();
+
const classes = makeStyles({
button: {
textTransform: "none",
@@ -66,61 +67,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..f58966c 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,8 +22,41 @@ export enum WindowType {
ManageTags = "ManageTags",
}
-export interface WindowState {
- tabLabel: string,
+export interface WindowState { }
+
+export function Window(props: {
+ type: WindowType,
+ stateOverride: any,
+}) {
+ const [state, dispatch] = useReducer(
+ newWindowReducer[props.type],
+ newWindowState[props.type](),
+ );
+ const _state: any = {
+ ...state,
+ ...props.stateOverride
+ };
+
+ if (props.type === WindowType.Query) {
+ return
+ }
+ if (props.type === WindowType.Artist) {
+ return
+ }
+ if (props.type === WindowType.Album) {
+ return
+ }
+ if (props.type === WindowType.ManageTags) {
+ return
+ }
+ if (props.type === WindowType.Song) {
+ return
+ }
+ if (props.type === WindowType.Tag) {
+ return
+ }
+
+ throw new Error("Unsupported window type")
}
export const newWindowReducer = {
@@ -38,7 +71,6 @@ export const newWindowReducer = {
export const newWindowState = {
[WindowType.Query]: () => {
return {
- tabLabel: <>Query>,
editingQuery: false,
query: null,
resultsForQuery: null,
@@ -46,8 +78,7 @@ export const newWindowState = {
},
[WindowType.Artist]: () => {
return {
- tabLabel: <>Artist 1>,
- artistId: 1,
+ id: 1,
metadata: null,
pendingChanges: null,
songGetters: songGetters,
@@ -56,8 +87,7 @@ export const newWindowState = {
},
[WindowType.Album]: () => {
return {
- tabLabel: <>Album 1>,
- albumId: 1,
+ id: 1,
metadata: null,
pendingChanges: null,
songGetters: songGetters,
@@ -66,16 +96,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 +112,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..0d10dee 100644
--- a/client/src/components/windows/album/AlbumWindow.tsx
+++ b/client/src/components/windows/album/AlbumWindow.tsx
@@ -16,7 +16,7 @@ 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,
@@ -48,7 +48,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) {
@@ -69,7 +68,7 @@ export default function AlbumWindow(props: IProps) {
// 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 +85,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,
@@ -135,7 +134,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 +184,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..b426ec3 100644
--- a/client/src/components/windows/artist/ArtistWindow.tsx
+++ b/client/src/components/windows/artist/ArtistWindow.tsx
@@ -16,7 +16,7 @@ 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 +48,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) {
@@ -69,7 +68,7 @@ export default function ArtistWindow(props: IProps) {
// 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 +85,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,
@@ -135,7 +134,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 +184,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: {
diff --git a/client/src/components/windows/song/SongWindow.tsx b/client/src/components/windows/song/SongWindow.tsx
index e7048aa..3a54b5e 100644
--- a/client/src/components/windows/song/SongWindow.tsx
+++ b/client/src/components/windows/song/SongWindow.tsx
@@ -18,7 +18,7 @@ export type SongMetadata = serverApi.SongDetails;
export type SongMetadataChanges = serverApi.ModifySongRequest;
export interface SongWindowState extends WindowState {
- songId: number,
+ id: number,
metadata: SongMetadata | null,
pendingChanges: SongMetadataChanges | null,
}
@@ -45,7 +45,6 @@ export function SongWindowReducer(state: SongWindowState, action: any) {
export interface IProps {
state: SongWindowState,
dispatch: (action: any) => void,
- mainDispatch: (action: any) => void,
}
export async function getSongMetadata(id: number) {
@@ -65,7 +64,7 @@ export default function SongWindow(props: IProps) {
let pendingChanges = props.state.pendingChanges;
useEffect(() => {
- getSongMetadata(props.state.songId)
+ getSongMetadata(props.state.id)
.then((m: SongMetadata) => {
props.dispatch({
type: SongWindowStateActions.SetMetadata,
@@ -122,7 +121,7 @@ export default function SongWindow(props: IProps) {
{
setApplying(true);
- saveSongChanges(props.state.songId, pendingChanges || {})
+ saveSongChanges(props.state.id, pendingChanges || {})
.then(() => {
setApplying(false);
props.dispatch({
diff --git a/client/src/components/windows/tag/TagWindow.tsx b/client/src/components/windows/tag/TagWindow.tsx
index ec33f91..3ec693e 100644
--- a/client/src/components/windows/tag/TagWindow.tsx
+++ b/client/src/components/windows/tag/TagWindow.tsx
@@ -21,7 +21,7 @@ export type TagMetadata = FullTagMetadata;
export type TagMetadataChanges = serverApi.ModifyTagRequest;
export interface TagWindowState extends WindowState {
- tagId: number,
+ id: number,
metadata: TagMetadata | null,
pendingChanges: TagMetadataChanges | null,
songsWithTag: any[] | null,
@@ -53,7 +53,6 @@ export function TagWindowReducer(state: TagWindowState, action: any) {
export interface IProps {
state: TagWindowState,
dispatch: (action: any) => void,
- mainDispatch: (action: any) => void,
}
export async function getTagMetadata(id: number) {
@@ -86,7 +85,7 @@ export default function TagWindow(props: IProps) {
// Effect to get the tag's metadata.
useEffect(() => {
- getTagMetadata(props.state.tagId)
+ getTagMetadata(props.state.id)
.then((m: TagMetadata) => {
props.dispatch({
type: TagWindowStateActions.SetMetadata,
@@ -103,7 +102,7 @@ export default function TagWindow(props: IProps) {
const songs = await querySongs({
query: {
a: QueryLeafBy.TagId,
- b: props.state.tagId,
+ b: props.state.id,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
@@ -163,7 +162,7 @@ export default function TagWindow(props: IProps) {
{
setApplying(true);
- saveTagChanges(props.state.tagId, pendingChanges || {})
+ saveTagChanges(props.state.id, pendingChanges || {})
.then(() => {
setApplying(false);
props.dispatch({
@@ -213,7 +212,6 @@ export default function TagWindow(props: IProps) {
{props.state.songsWithTag && }
{!props.state.songsWithTag && }