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 <> - - error - + + + error + + 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: { { props.onClose(); - props.onOpenInTab(); + props.onOpenTag(); }} >Browse void, - mainDispatch: (action: any) => void, state: ManageTagsWindowState, changedTags: any[], }) { @@ -131,6 +130,8 @@ export function SingleTag(props: { const [expanded, setExpanded] = useState(false); const theme = useTheme(); + const history = useHistory(); + const onOpenMenu = (e: any) => { setMenuPos([e.clientX, e.clientY]) }; @@ -169,7 +170,6 @@ export function SingleTag(props: { , /]} dispatch={props.dispatch} - mainDispatch={props.mainDispatch} state={props.state} changedTags={props.changedTags} />)} @@ -177,19 +177,8 @@ export function SingleTag(props: { position={menuPos} open={menuPos !== null} onClose={onCloseMenu} - onOpenInTab={() => { - props.mainDispatch({ - type: MainWindowStateActions.AddTab, - tabState: { - tabLabel: <>{tag.name}, - tagId: tag.tagId, - metadata: null, - songGetters: songGetters, - songsWithTag: null, - }, - tabReducer: newWindowReducer[WindowType.Tag], - tabType: WindowType.Tag, - }) + onOpenTag={() => { + history.push('/tag/' + tag.tagId); }} onRename={(s: string) => { props.dispatch({ @@ -352,7 +341,6 @@ function applyTagsChanges(tags: Record, changes: TagChange[]) { export default function ManageTagsWindow(props: { state: ManageTagsWindowState, dispatch: (action: any) => void, - mainDispatch: (action: any) => void, }) { const [newTagMenuPos, setNewTagMenuPos] = React.useState(null); @@ -447,7 +435,6 @@ export default function ManageTagsWindow(props: { tag={tag} prependElems={[]} dispatch={props.dispatch} - mainDispatch={props.mainDispatch} state={props.state} changedTags={changedTags} />; diff --git a/client/src/components/windows/query/QueryWindow.tsx b/client/src/components/windows/query/QueryWindow.tsx index b410a9d..74f6bd9 100644 --- a/client/src/components/windows/query/QueryWindow.tsx +++ b/client/src/components/windows/query/QueryWindow.tsx @@ -102,7 +102,6 @@ export function QueryWindowReducer(state: QueryWindowState, action: any) { export interface IProps { state: QueryWindowState, dispatch: (action: any) => void, - mainDispatch: (action: any) => void, } export default function QueryWindow(props: IProps) { @@ -170,7 +169,6 @@ export default function QueryWindow(props: IProps) { {loading && } 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 && }