Go back to declarative routing. No more tabs.

pull/28/head
Sander Vocke 5 years ago
parent a689613a45
commit c9336af0f3
  1. 12
      client/src/App.tsx
  2. 185
      client/src/components/MainWindow.tsx
  3. 3
      client/src/components/appbar/AppBar.tsx
  4. 55
      client/src/components/tables/ResultsTable.tsx
  5. 66
      client/src/components/windows/Windows.tsx
  6. 10
      client/src/components/windows/album/AlbumWindow.tsx
  7. 10
      client/src/components/windows/artist/ArtistWindow.tsx
  8. 4
      client/src/components/windows/manage_tags/ManageTagMenu.tsx
  9. 23
      client/src/components/windows/manage_tags/ManageTagsWindow.tsx
  10. 2
      client/src/components/windows/query/QueryWindow.tsx
  11. 7
      client/src/components/windows/song/SongWindow.tsx
  12. 10
      client/src/components/windows/tag/TagWindow.tsx

@ -3,25 +3,13 @@ import React from 'react';
import { DndProvider } from 'react-dnd'; import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import {
HashRouter as Router,
Switch,
Route
} from "react-router-dom";
import MainWindow from './components/MainWindow'; import MainWindow from './components/MainWindow';
function App() { function App() {
return ( return (
<Router>
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<Switch>
<Route path="/">
<MainWindow /> <MainWindow />
</Route>
</Switch>
</DndProvider> </DndProvider>
</Router>
); );
} }

@ -2,14 +2,15 @@ import React, { useReducer, Reducer } from 'react';
import { ThemeProvider, CssBaseline, createMuiTheme } from '@material-ui/core'; import { ThemeProvider, CssBaseline, createMuiTheme } from '@material-ui/core';
import { grey } from '@material-ui/core/colors'; import { grey } from '@material-ui/core/colors';
import AppBar from './appbar/AppBar'; import AppBar from './appbar/AppBar';
import QueryWindow from './windows/query/QueryWindow'; import QueryWindow, { QueryWindowReducer } from './windows/query/QueryWindow';
import { NewTabProps } from './appbar/AddTabMenu'; 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 ArtistWindow from './windows/artist/ArtistWindow';
import AlbumWindow from './windows/album/AlbumWindow'; import AlbumWindow from './windows/album/AlbumWindow';
import TagWindow from './windows/tag/TagWindow'; import TagWindow from './windows/tag/TagWindow';
import SongWindow from './windows/song/SongWindow'; import SongWindow from './windows/song/SongWindow';
import ManageTagsWindow from './windows/manage_tags/ManageTagsWindow'; import ManageTagsWindow from './windows/manage_tags/ManageTagsWindow';
import { BrowserRouter, Switch, Route, useParams, Redirect } from 'react-router-dom';
var _ = require('lodash'); var _ = require('lodash');
const darkTheme = createMuiTheme({ const darkTheme = createMuiTheme({
@ -21,150 +22,56 @@ const darkTheme = createMuiTheme({
}, },
}); });
export interface MainWindowState { function WindowContent(props: {
tabStates: any[], type: WindowType,
tabReducers: Reducer<any, any>[], }) {
tabTypes: WindowType[], const { id } = useParams();
activeTab: number, return <Window type={props.type} stateOverride={id ? { "id": id } : {}} />
}
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) { export default function MainWindow(props: any) {
const [state, dispatch] = useReducer(MainWindowReducer, { const [windowState, windowDispatch] = useReducer(
tabStates: [ QueryWindowReducer,
newWindowState[WindowType.Query](), {
newWindowState[WindowType.Song](), editingQuery: false,
newWindowState[WindowType.Album](), query: null,
newWindowState[WindowType.Artist](), resultsForQuery: null,
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 <QueryWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
case WindowType.Artist:
return <ArtistWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
case WindowType.Album:
return <AlbumWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
case WindowType.Tag:
return <TagWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
case WindowType.Song:
return <SongWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
case WindowType.ManageTags:
return <ManageTagsWindow
state={tabState}
dispatch={tabDispatch}
mainDispatch={dispatch}
/>
default:
throw new Error("Unimplemented window type");
}
});
return <ThemeProvider theme={darkTheme}> return <ThemeProvider theme={darkTheme}>
<CssBaseline /> <CssBaseline />
<BrowserRouter>
<AppBar <AppBar
tabLabels={state.tabStates.map((s: any) => s.tabLabel)} tabLabels={[]}
selectedTab={state.activeTab} selectedTab={0}
setSelectedTab={(t: number) => dispatch({ type: MainWindowStateActions.SetActiveTab, value: t })} setSelectedTab={(t: number) => { }}
onCloseTab={(t: number) => dispatch({ type: MainWindowStateActions.CloseTab, idx: t })} onCloseTab={(t: number) => { }}
onAddTab={(w: NewTabProps) => { onAddTab={(w: NewTabProps) => { }}
dispatch({
type: MainWindowStateActions.AddTab,
tabState: newWindowState[w.windowType](),
tabReducer: newWindowReducer[w.windowType],
tabType: w.windowType,
})
}}
/> />
{windows[state.activeTab]} <Switch>
<Route exact path="/">
<Redirect to={"/query"} />
</Route>
<Route path="/query">
<WindowContent type={WindowType.Query} />
</Route>
<Route path="/artist/:id">
<WindowContent type={WindowType.Artist} />
</Route>
<Route path="/tag/:id">
<WindowContent type={WindowType.Tag} />
</Route>
<Route path="/album/:id">
<WindowContent type={WindowType.Album} />
</Route>
<Route path="/song/:id">
<WindowContent type={WindowType.Song} />
</Route>
<Route path="/tags">
<WindowContent type={WindowType.ManageTags} />
</Route>
</Switch>
</BrowserRouter>
</ThemeProvider> </ThemeProvider>
} }

@ -3,6 +3,7 @@ import { AppBar as MuiAppBar, Box, Tab as MuiTab, Tabs, IconButton } from '@mate
import CloseIcon from '@material-ui/icons/Close'; import CloseIcon from '@material-ui/icons/Close';
import AddIcon from '@material-ui/icons/Add'; import AddIcon from '@material-ui/icons/Add';
import AddTabMenu, { NewTabProps } from './AddTabMenu'; import AddTabMenu, { NewTabProps } from './AddTabMenu';
import { Link } from 'react-router-dom';
export interface IProps { export interface IProps {
tabLabels: string[], tabLabels: string[],
@ -58,9 +59,11 @@ export default function AppBar(props: IProps) {
return <> return <>
<MuiAppBar position="static" style={{ background: 'grey' }}> <MuiAppBar position="static" style={{ background: 'grey' }}>
<Box display="flex" alignItems="center"> <Box display="flex" alignItems="center">
<Link to="/">
<Box m={0.5} display="flex" alignItems="center"> <Box m={0.5} display="flex" alignItems="center">
<img height="30px" src={process.env.PUBLIC_URL + "/logo.svg"} alt="error"></img> <img height="30px" src={process.env.PUBLIC_URL + "/logo.svg"} alt="error"></img>
</Box> </Box>
</Link>
<Tabs <Tabs
value={props.selectedTab} value={props.selectedTab}
onChange={(e: any, v: number) => props.setSelectedTab(v)} onChange={(e: any, v: number) => props.setSelectedTab(v)}

@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import { TableContainer, Table, TableHead, TableRow, TableCell, Paper, makeStyles, TableBody, Chip, Box, Button } from '@material-ui/core'; import { TableContainer, Table, TableHead, TableRow, TableCell, Paper, makeStyles, TableBody, Chip, Box, Button } from '@material-ui/core';
import stringifyList from '../../lib/stringifyList'; import stringifyList from '../../lib/stringifyList';
import { MainWindowStateActions } from '../MainWindow';
import { newWindowReducer, WindowType } from '../windows/Windows'; import { newWindowReducer, WindowType } from '../windows/Windows';
import PersonIcon from '@material-ui/icons/Person'; import PersonIcon from '@material-ui/icons/Person';
import AlbumIcon from '@material-ui/icons/Album'; import AlbumIcon from '@material-ui/icons/Album';
import AudiotrackIcon from '@material-ui/icons/Audiotrack'; import AudiotrackIcon from '@material-ui/icons/Audiotrack';
import LocalOfferIcon from '@material-ui/icons/LocalOffer'; import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import { songGetters } from '../../lib/songGetters'; import { songGetters } from '../../lib/songGetters';
import { Redirect, useHistory } from 'react-router';
export interface SongGetters { export interface SongGetters {
getTitle: (song: any) => string, getTitle: (song: any) => string,
@ -23,10 +23,11 @@ export interface SongGetters {
export interface IProps { export interface IProps {
songs: any[], songs: any[],
songGetters: SongGetters, songGetters: SongGetters,
mainDispatch: (action: any) => void,
} }
export default function SongTable(props: IProps) { export default function SongTable(props: IProps) {
const history = useHistory();
const classes = makeStyles({ const classes = makeStyles({
button: { button: {
textTransform: "none", textTransform: "none",
@ -66,61 +67,19 @@ export default function SongTable(props: IProps) {
const tagIds = props.songGetters.getTagIds(song); const tagIds = props.songGetters.getTagIds(song);
const onClickArtist = () => { const onClickArtist = () => {
props.mainDispatch({ history.push('/artist/' + mainArtistId);
type: MainWindowStateActions.AddTab,
tabState: {
tabLabel: <><PersonIcon />{mainArtistName}</>,
artistId: mainArtistId,
metadata: null,
songGetters: songGetters,
songsByArtist: null,
},
tabReducer: newWindowReducer[WindowType.Artist],
tabType: WindowType.Artist,
})
} }
const onClickAlbum = () => { const onClickAlbum = () => {
props.mainDispatch({ history.push('/album/' + mainAlbumId);
type: MainWindowStateActions.AddTab,
tabState: {
tabLabel: <><AlbumIcon />{mainAlbumName}</>,
albumId: mainAlbumId,
metadata: null,
songGetters: songGetters,
songsOnAlbum: null,
},
tabReducer: newWindowReducer[WindowType.Album],
tabType: WindowType.Album,
})
} }
const onClickSong = () => { const onClickSong = () => {
props.mainDispatch({ history.push('/song/' + songId);
type: MainWindowStateActions.AddTab,
tabState: {
tabLabel: <><AudiotrackIcon />{title}</>,
songId: songId,
metadata: null,
},
tabReducer: newWindowReducer[WindowType.Song],
tabType: WindowType.Song,
})
} }
const onClickTag = (id: number, name: string) => { const onClickTag = (id: number, name: string) => {
props.mainDispatch({ history.push('/tag/' + id);
type: MainWindowStateActions.AddTab,
tabState: {
tabLabel: <><LocalOfferIcon />{name}</>,
tagId: id,
metadata: null,
songGetters: songGetters,
songsWithTag: null,
},
tabReducer: newWindowReducer[WindowType.Tag],
tabType: WindowType.Tag,
})
} }
const tags = props.songGetters.getTagNames(song).map((tag: string[], i: number) => { const tags = props.songGetters.getTagNames(song).map((tag: string[], i: number) => {

@ -1,17 +1,17 @@
import React from 'react'; import React, { useReducer } from 'react';
import { QueryWindowReducer } from "./query/QueryWindow"; import QueryWindow, { QueryWindowReducer } from "./query/QueryWindow";
import { ArtistWindowReducer } from "./artist/ArtistWindow"; import ArtistWindow, { ArtistWindowReducer } from "./artist/ArtistWindow";
import SearchIcon from '@material-ui/icons/Search'; import SearchIcon from '@material-ui/icons/Search';
import PersonIcon from '@material-ui/icons/Person'; import PersonIcon from '@material-ui/icons/Person';
import AlbumIcon from '@material-ui/icons/Album'; import AlbumIcon from '@material-ui/icons/Album';
import LocalOfferIcon from '@material-ui/icons/LocalOffer'; import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import AudiotrackIcon from '@material-ui/icons/Audiotrack'; import AudiotrackIcon from '@material-ui/icons/Audiotrack';
import LoyaltyIcon from '@material-ui/icons/Loyalty'; import LoyaltyIcon from '@material-ui/icons/Loyalty';
import { SongWindowReducer } from './song/SongWindow'; import SongWindow, { SongWindowReducer } from './song/SongWindow';
import { AlbumWindowReducer } from './album/AlbumWindow'; import AlbumWindow, { AlbumWindowReducer } from './album/AlbumWindow';
import { TagWindowReducer } from './tag/TagWindow'; import TagWindow, { TagWindowReducer } from './tag/TagWindow';
import { songGetters } from '../../lib/songGetters'; import { songGetters } from '../../lib/songGetters';
import { ManageTagsWindowReducer } from './manage_tags/ManageTagsWindow'; import ManageTagsWindow, { ManageTagsWindowReducer } from './manage_tags/ManageTagsWindow';
export enum WindowType { export enum WindowType {
Query = "Query", Query = "Query",
@ -22,8 +22,41 @@ export enum WindowType {
ManageTags = "ManageTags", ManageTags = "ManageTags",
} }
export interface WindowState { export interface WindowState { }
tabLabel: string,
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 <QueryWindow state={_state} dispatch={dispatch}/>
}
if (props.type === WindowType.Artist) {
return <ArtistWindow state={_state} dispatch={dispatch}/>
}
if (props.type === WindowType.Album) {
return <AlbumWindow state={_state} dispatch={dispatch}/>
}
if (props.type === WindowType.ManageTags) {
return <ManageTagsWindow state={_state} dispatch={dispatch}/>
}
if (props.type === WindowType.Song) {
return <SongWindow state={_state} dispatch={dispatch}/>
}
if (props.type === WindowType.Tag) {
return <TagWindow state={_state} dispatch={dispatch}/>
}
throw new Error("Unsupported window type")
} }
export const newWindowReducer = { export const newWindowReducer = {
@ -38,7 +71,6 @@ export const newWindowReducer = {
export const newWindowState = { export const newWindowState = {
[WindowType.Query]: () => { [WindowType.Query]: () => {
return { return {
tabLabel: <><SearchIcon/>Query</>,
editingQuery: false, editingQuery: false,
query: null, query: null,
resultsForQuery: null, resultsForQuery: null,
@ -46,8 +78,7 @@ export const newWindowState = {
}, },
[WindowType.Artist]: () => { [WindowType.Artist]: () => {
return { return {
tabLabel: <><PersonIcon/>Artist 1</>, id: 1,
artistId: 1,
metadata: null, metadata: null,
pendingChanges: null, pendingChanges: null,
songGetters: songGetters, songGetters: songGetters,
@ -56,8 +87,7 @@ export const newWindowState = {
}, },
[WindowType.Album]: () => { [WindowType.Album]: () => {
return { return {
tabLabel: <><AlbumIcon/>Album 1</>, id: 1,
albumId: 1,
metadata: null, metadata: null,
pendingChanges: null, pendingChanges: null,
songGetters: songGetters, songGetters: songGetters,
@ -66,16 +96,14 @@ export const newWindowState = {
}, },
[WindowType.Song]: () => { [WindowType.Song]: () => {
return { return {
tabLabel: <><AudiotrackIcon/>Song 1</>, id: 1,
songId: 1,
metadata: null, metadata: null,
pendingChanges: null, pendingChanges: null,
} }
}, },
[WindowType.Tag]: () => { [WindowType.Tag]: () => {
return { return {
tabLabel: <><LocalOfferIcon/>Tag 1</>, id: 1,
tagId: 1,
metadata: null, metadata: null,
pendingChanges: null, pendingChanges: null,
songGetters: songGetters, songGetters: songGetters,
@ -84,8 +112,8 @@ export const newWindowState = {
}, },
[WindowType.ManageTags]: () => { [WindowType.ManageTags]: () => {
return { return {
tabLabel: <><LoyaltyIcon/>Manage Tags</>,
fetchedTags: null, fetchedTags: null,
alert: null,
pendingChanges: [], pendingChanges: [],
} }
} }

@ -16,7 +16,7 @@ export type AlbumMetadata = serverApi.AlbumDetails;
export type AlbumMetadataChanges = serverApi.ModifyAlbumRequest; export type AlbumMetadataChanges = serverApi.ModifyAlbumRequest;
export interface AlbumWindowState extends WindowState { export interface AlbumWindowState extends WindowState {
albumId: number, id: number,
metadata: AlbumMetadata | null, metadata: AlbumMetadata | null,
pendingChanges: AlbumMetadataChanges | null, pendingChanges: AlbumMetadataChanges | null,
songsOnAlbum: any[] | null, songsOnAlbum: any[] | null,
@ -48,7 +48,6 @@ export function AlbumWindowReducer(state: AlbumWindowState, action: any) {
export interface IProps { export interface IProps {
state: AlbumWindowState, state: AlbumWindowState,
dispatch: (action: any) => void, dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
} }
export async function getAlbumMetadata(id: number) { export async function getAlbumMetadata(id: number) {
@ -69,7 +68,7 @@ export default function AlbumWindow(props: IProps) {
// Effect to get the album's metadata. // Effect to get the album's metadata.
useEffect(() => { useEffect(() => {
getAlbumMetadata(props.state.albumId) getAlbumMetadata(props.state.id)
.then((m: AlbumMetadata) => { .then((m: AlbumMetadata) => {
props.dispatch({ props.dispatch({
type: AlbumWindowStateActions.SetMetadata, type: AlbumWindowStateActions.SetMetadata,
@ -86,7 +85,7 @@ export default function AlbumWindow(props: IProps) {
const songs = await querySongs({ const songs = await querySongs({
query: { query: {
a: QueryLeafBy.AlbumId, a: QueryLeafBy.AlbumId,
b: props.state.albumId, b: props.state.id,
leafOp: QueryLeafOp.Equals, leafOp: QueryLeafOp.Equals,
}, },
offset: 0, offset: 0,
@ -135,7 +134,7 @@ export default function AlbumWindow(props: IProps) {
<Box> <Box>
<SubmitChangesButton onClick={() => { <SubmitChangesButton onClick={() => {
setApplying(true); setApplying(true);
saveAlbumChanges(props.state.albumId, pendingChanges || {}) saveAlbumChanges(props.state.id, pendingChanges || {})
.then(() => { .then(() => {
setApplying(false); setApplying(false);
props.dispatch({ props.dispatch({
@ -185,7 +184,6 @@ export default function AlbumWindow(props: IProps) {
{props.state.songsOnAlbum && <SongTable {props.state.songsOnAlbum && <SongTable
songs={props.state.songsOnAlbum} songs={props.state.songsOnAlbum}
songGetters={props.state.songGetters} songGetters={props.state.songGetters}
mainDispatch={props.mainDispatch}
/>} />}
{!props.state.songsOnAlbum && <CircularProgress />} {!props.state.songsOnAlbum && <CircularProgress />}
</Box> </Box>

@ -16,7 +16,7 @@ export type ArtistMetadata = serverApi.ArtistDetails;
export type ArtistMetadataChanges = serverApi.ModifyArtistRequest; export type ArtistMetadataChanges = serverApi.ModifyArtistRequest;
export interface ArtistWindowState extends WindowState { export interface ArtistWindowState extends WindowState {
artistId: number, id: number,
metadata: ArtistMetadata | null, metadata: ArtistMetadata | null,
pendingChanges: ArtistMetadataChanges | null, pendingChanges: ArtistMetadataChanges | null,
songsByArtist: any[] | null, songsByArtist: any[] | null,
@ -48,7 +48,6 @@ export function ArtistWindowReducer(state: ArtistWindowState, action: any) {
export interface IProps { export interface IProps {
state: ArtistWindowState, state: ArtistWindowState,
dispatch: (action: any) => void, dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
} }
export async function getArtistMetadata(id: number) { export async function getArtistMetadata(id: number) {
@ -69,7 +68,7 @@ export default function ArtistWindow(props: IProps) {
// Effect to get the artist's metadata. // Effect to get the artist's metadata.
useEffect(() => { useEffect(() => {
getArtistMetadata(props.state.artistId) getArtistMetadata(props.state.id)
.then((m: ArtistMetadata) => { .then((m: ArtistMetadata) => {
props.dispatch({ props.dispatch({
type: ArtistWindowStateActions.SetMetadata, type: ArtistWindowStateActions.SetMetadata,
@ -86,7 +85,7 @@ export default function ArtistWindow(props: IProps) {
const songs = await querySongs({ const songs = await querySongs({
query: { query: {
a: QueryLeafBy.ArtistId, a: QueryLeafBy.ArtistId,
b: props.state.artistId, b: props.state.id,
leafOp: QueryLeafOp.Equals, leafOp: QueryLeafOp.Equals,
}, },
offset: 0, offset: 0,
@ -135,7 +134,7 @@ export default function ArtistWindow(props: IProps) {
<Box> <Box>
<SubmitChangesButton onClick={() => { <SubmitChangesButton onClick={() => {
setApplying(true); setApplying(true);
saveArtistChanges(props.state.artistId, pendingChanges || {}) saveArtistChanges(props.state.id, pendingChanges || {})
.then(() => { .then(() => {
setApplying(false); setApplying(false);
props.dispatch({ props.dispatch({
@ -185,7 +184,6 @@ export default function ArtistWindow(props: IProps) {
{props.state.songsByArtist && <SongTable {props.state.songsByArtist && <SongTable
songs={props.state.songsByArtist} songs={props.state.songsByArtist}
songGetters={props.state.songGetters} songGetters={props.state.songGetters}
mainDispatch={props.mainDispatch}
/>} />}
{!props.state.songsByArtist && <CircularProgress />} {!props.state.songsByArtist && <CircularProgress />}
</Box> </Box>

@ -35,7 +35,7 @@ export default function ManageTagMenu(props: {
onDelete: () => void, onDelete: () => void,
onMove: (to: string | null) => void, onMove: (to: string | null) => void,
onMergeInto: (to: string) => void, onMergeInto: (to: string) => void,
onOpenInTab: () => void, onOpenTag: () => void,
tag: any, tag: any,
changedTags: any[], // Tags organized hierarchically with "children" fields changedTags: any[], // Tags organized hierarchically with "children" fields
}) { }) {
@ -53,7 +53,7 @@ export default function ManageTagMenu(props: {
<MenuItem <MenuItem
onClick={() => { onClick={() => {
props.onClose(); props.onClose();
props.onOpenInTab(); props.onOpenTag();
}} }}
>Browse</MenuItem> >Browse</MenuItem>
<MenuItem <MenuItem

@ -9,10 +9,10 @@ import ControlTagChanges, { TagChange, TagChangeType, submitTagChanges } from '.
import { queryTags } from '../../../lib/backend/queries'; import { queryTags } from '../../../lib/backend/queries';
import NewTagMenu from './NewTagMenu'; import NewTagMenu from './NewTagMenu';
import { v4 as genUuid } from 'uuid'; import { v4 as genUuid } from 'uuid';
import { MainWindowStateActions } from '../../MainWindow';
import LocalOfferIcon from '@material-ui/icons/LocalOffer'; import LocalOfferIcon from '@material-ui/icons/LocalOffer';
import { songGetters } from '../../../lib/songGetters'; import { songGetters } from '../../../lib/songGetters';
import Alert from '@material-ui/lab/Alert'; import Alert from '@material-ui/lab/Alert';
import { useHistory } from 'react-router';
var _ = require('lodash'); var _ = require('lodash');
export interface ManageTagsWindowState extends WindowState { export interface ManageTagsWindowState extends WindowState {
@ -120,7 +120,6 @@ export function SingleTag(props: {
tag: any, tag: any,
prependElems: any[], prependElems: any[],
dispatch: (action: any) => void, dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
state: ManageTagsWindowState, state: ManageTagsWindowState,
changedTags: any[], changedTags: any[],
}) { }) {
@ -131,6 +130,8 @@ export function SingleTag(props: {
const [expanded, setExpanded] = useState<boolean>(false); const [expanded, setExpanded] = useState<boolean>(false);
const theme = useTheme(); const theme = useTheme();
const history = useHistory();
const onOpenMenu = (e: any) => { const onOpenMenu = (e: any) => {
setMenuPos([e.clientX, e.clientY]) setMenuPos([e.clientX, e.clientY])
}; };
@ -169,7 +170,6 @@ export function SingleTag(props: {
<TagChip transparent={true} label={tagLabel} />, <TagChip transparent={true} label={tagLabel} />,
<Typography variant="h5">/</Typography>]} <Typography variant="h5">/</Typography>]}
dispatch={props.dispatch} dispatch={props.dispatch}
mainDispatch={props.mainDispatch}
state={props.state} state={props.state}
changedTags={props.changedTags} changedTags={props.changedTags}
/>)} />)}
@ -177,19 +177,8 @@ export function SingleTag(props: {
position={menuPos} position={menuPos}
open={menuPos !== null} open={menuPos !== null}
onClose={onCloseMenu} onClose={onCloseMenu}
onOpenInTab={() => { onOpenTag={() => {
props.mainDispatch({ history.push('/tag/' + tag.tagId);
type: MainWindowStateActions.AddTab,
tabState: {
tabLabel: <><LocalOfferIcon />{tag.name}</>,
tagId: tag.tagId,
metadata: null,
songGetters: songGetters,
songsWithTag: null,
},
tabReducer: newWindowReducer[WindowType.Tag],
tabType: WindowType.Tag,
})
}} }}
onRename={(s: string) => { onRename={(s: string) => {
props.dispatch({ props.dispatch({
@ -352,7 +341,6 @@ function applyTagsChanges(tags: Record<string, any>, changes: TagChange[]) {
export default function ManageTagsWindow(props: { export default function ManageTagsWindow(props: {
state: ManageTagsWindowState, state: ManageTagsWindowState,
dispatch: (action: any) => void, dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
}) { }) {
const [newTagMenuPos, setNewTagMenuPos] = React.useState<null | number[]>(null); const [newTagMenuPos, setNewTagMenuPos] = React.useState<null | number[]>(null);
@ -447,7 +435,6 @@ export default function ManageTagsWindow(props: {
tag={tag} tag={tag}
prependElems={[]} prependElems={[]}
dispatch={props.dispatch} dispatch={props.dispatch}
mainDispatch={props.mainDispatch}
state={props.state} state={props.state}
changedTags={changedTags} changedTags={changedTags}
/>; />;

@ -102,7 +102,6 @@ export function QueryWindowReducer(state: QueryWindowState, action: any) {
export interface IProps { export interface IProps {
state: QueryWindowState, state: QueryWindowState,
dispatch: (action: any) => void, dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
} }
export default function QueryWindow(props: IProps) { export default function QueryWindow(props: IProps) {
@ -170,7 +169,6 @@ export default function QueryWindow(props: IProps) {
<SongTable <SongTable
songs={showResults} songs={showResults}
songGetters={songGetters} songGetters={songGetters}
mainDispatch={props.mainDispatch}
/> />
{loading && <LinearProgress />} {loading && <LinearProgress />}
</Box> </Box>

@ -18,7 +18,7 @@ export type SongMetadata = serverApi.SongDetails;
export type SongMetadataChanges = serverApi.ModifySongRequest; export type SongMetadataChanges = serverApi.ModifySongRequest;
export interface SongWindowState extends WindowState { export interface SongWindowState extends WindowState {
songId: number, id: number,
metadata: SongMetadata | null, metadata: SongMetadata | null,
pendingChanges: SongMetadataChanges | null, pendingChanges: SongMetadataChanges | null,
} }
@ -45,7 +45,6 @@ export function SongWindowReducer(state: SongWindowState, action: any) {
export interface IProps { export interface IProps {
state: SongWindowState, state: SongWindowState,
dispatch: (action: any) => void, dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
} }
export async function getSongMetadata(id: number) { export async function getSongMetadata(id: number) {
@ -65,7 +64,7 @@ export default function SongWindow(props: IProps) {
let pendingChanges = props.state.pendingChanges; let pendingChanges = props.state.pendingChanges;
useEffect(() => { useEffect(() => {
getSongMetadata(props.state.songId) getSongMetadata(props.state.id)
.then((m: SongMetadata) => { .then((m: SongMetadata) => {
props.dispatch({ props.dispatch({
type: SongWindowStateActions.SetMetadata, type: SongWindowStateActions.SetMetadata,
@ -122,7 +121,7 @@ export default function SongWindow(props: IProps) {
<Box> <Box>
<SubmitChangesButton onClick={() => { <SubmitChangesButton onClick={() => {
setApplying(true); setApplying(true);
saveSongChanges(props.state.songId, pendingChanges || {}) saveSongChanges(props.state.id, pendingChanges || {})
.then(() => { .then(() => {
setApplying(false); setApplying(false);
props.dispatch({ props.dispatch({

@ -21,7 +21,7 @@ export type TagMetadata = FullTagMetadata;
export type TagMetadataChanges = serverApi.ModifyTagRequest; export type TagMetadataChanges = serverApi.ModifyTagRequest;
export interface TagWindowState extends WindowState { export interface TagWindowState extends WindowState {
tagId: number, id: number,
metadata: TagMetadata | null, metadata: TagMetadata | null,
pendingChanges: TagMetadataChanges | null, pendingChanges: TagMetadataChanges | null,
songsWithTag: any[] | null, songsWithTag: any[] | null,
@ -53,7 +53,6 @@ export function TagWindowReducer(state: TagWindowState, action: any) {
export interface IProps { export interface IProps {
state: TagWindowState, state: TagWindowState,
dispatch: (action: any) => void, dispatch: (action: any) => void,
mainDispatch: (action: any) => void,
} }
export async function getTagMetadata(id: number) { export async function getTagMetadata(id: number) {
@ -86,7 +85,7 @@ export default function TagWindow(props: IProps) {
// Effect to get the tag's metadata. // Effect to get the tag's metadata.
useEffect(() => { useEffect(() => {
getTagMetadata(props.state.tagId) getTagMetadata(props.state.id)
.then((m: TagMetadata) => { .then((m: TagMetadata) => {
props.dispatch({ props.dispatch({
type: TagWindowStateActions.SetMetadata, type: TagWindowStateActions.SetMetadata,
@ -103,7 +102,7 @@ export default function TagWindow(props: IProps) {
const songs = await querySongs({ const songs = await querySongs({
query: { query: {
a: QueryLeafBy.TagId, a: QueryLeafBy.TagId,
b: props.state.tagId, b: props.state.id,
leafOp: QueryLeafOp.Equals, leafOp: QueryLeafOp.Equals,
}, },
offset: 0, offset: 0,
@ -163,7 +162,7 @@ export default function TagWindow(props: IProps) {
<Box> <Box>
<SubmitChangesButton onClick={() => { <SubmitChangesButton onClick={() => {
setApplying(true); setApplying(true);
saveTagChanges(props.state.tagId, pendingChanges || {}) saveTagChanges(props.state.id, pendingChanges || {})
.then(() => { .then(() => {
setApplying(false); setApplying(false);
props.dispatch({ props.dispatch({
@ -213,7 +212,6 @@ export default function TagWindow(props: IProps) {
{props.state.songsWithTag && <SongTable {props.state.songsWithTag && <SongTable
songs={props.state.songsWithTag} songs={props.state.songsWithTag}
songGetters={props.state.songGetters} songGetters={props.state.songGetters}
mainDispatch={props.mainDispatch}
/>} />}
{!props.state.songsWithTag && <CircularProgress />} {!props.state.songsWithTag && <CircularProgress />}
</Box> </Box>

Loading…
Cancel
Save