14 changed files with 739 additions and 81 deletions
@ -0,0 +1,32 @@ |
||||
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 <Menu |
||||
anchorEl={props.anchorEl} |
||||
keepMounted |
||||
open={Boolean(props.anchorEl)} |
||||
onClose={props.onClose} |
||||
> |
||||
<MenuItem disabled={true}>New Tab</MenuItem> |
||||
<MenuItem |
||||
onClick={() => { |
||||
props.onClose(); |
||||
props.onCreateTab({ |
||||
windowType: WindowType.Query, |
||||
}) |
||||
}} |
||||
>{WindowType.Query}</MenuItem> |
||||
</Menu> |
||||
} |
@ -0,0 +1,101 @@ |
||||
import React, { useEffect } from 'react'; |
||||
import { Box, Typography } from '@material-ui/core'; |
||||
import AlbumIcon from '@material-ui/icons/Album'; |
||||
import * as serverApi from '../../api'; |
||||
import { WindowState } from './Windows'; |
||||
|
||||
export interface AlbumMetadata { |
||||
name: string, |
||||
} |
||||
|
||||
export interface AlbumWindowState extends WindowState { |
||||
albumId: number, |
||||
metadata: AlbumMetadata | null, |
||||
} |
||||
|
||||
export enum AlbumWindowStateActions { |
||||
SetMetadata = "SetMetadata", |
||||
} |
||||
|
||||
export function AlbumWindowReducer(state: AlbumWindowState, action: any) { |
||||
switch (action.type) { |
||||
case AlbumWindowStateActions.SetMetadata: |
||||
return { ...state, metadata: action.value } |
||||
default: |
||||
throw new Error("Unimplemented AlbumWindow state update.") |
||||
} |
||||
} |
||||
|
||||
export interface IProps { |
||||
state: AlbumWindowState, |
||||
dispatch: (action: any) => void, |
||||
mainDispatch: (action: any) => void, |
||||
} |
||||
|
||||
export async function getAlbumMetadata(id: number) { |
||||
const query = { |
||||
prop: serverApi.QueryElemProperty.albumId, |
||||
propOperand: id, |
||||
propOperator: serverApi.QueryFilterOp.Eq, |
||||
}; |
||||
|
||||
var q: serverApi.QueryRequest = { |
||||
query: query, |
||||
offsetsLimits: { |
||||
albumOffset: 0, |
||||
albumLimit: 1, |
||||
}, |
||||
ordering: { |
||||
orderBy: { |
||||
type: serverApi.OrderByType.Name, |
||||
}, |
||||
ascending: true, |
||||
}, |
||||
}; |
||||
|
||||
const requestOpts = { |
||||
method: 'POST', |
||||
headers: { 'Content-Type': 'application/json' }, |
||||
body: JSON.stringify(q), |
||||
}; |
||||
|
||||
return (async () => { |
||||
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) |
||||
let json: any = await response.json(); |
||||
let album = json.albums[0]; |
||||
return { |
||||
name: album.name |
||||
} |
||||
})(); |
||||
} |
||||
|
||||
export default function AlbumWindow(props: IProps) { |
||||
let metadata = props.state.metadata; |
||||
|
||||
useEffect(() => { |
||||
getAlbumMetadata(props.state.albumId) |
||||
.then((m: AlbumMetadata) => { |
||||
console.log("metadata", m); |
||||
props.dispatch({ |
||||
type: AlbumWindowStateActions.SetMetadata, |
||||
value: m |
||||
}); |
||||
}) |
||||
}, [props.state.metadata?.name]); |
||||
|
||||
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
||||
<Box |
||||
m={1} |
||||
mt={4} |
||||
width="80%" |
||||
> |
||||
<AlbumIcon style={{ fontSize: 80 }}/> |
||||
</Box> |
||||
<Box |
||||
m={1} |
||||
width="80%" |
||||
> |
||||
{metadata && <Typography variant="h4">{metadata.name}</Typography>} |
||||
</Box> |
||||
</Box> |
||||
} |
@ -0,0 +1,101 @@ |
||||
import React, { useEffect } from 'react'; |
||||
import { Box, Typography } from '@material-ui/core'; |
||||
import PersonIcon from '@material-ui/icons/Person'; |
||||
import * as serverApi from '../../api'; |
||||
import { WindowState } from './Windows'; |
||||
|
||||
export interface ArtistMetadata { |
||||
name: string, |
||||
} |
||||
|
||||
export interface ArtistWindowState extends WindowState { |
||||
artistId: number, |
||||
metadata: ArtistMetadata | null, |
||||
} |
||||
|
||||
export enum ArtistWindowStateActions { |
||||
SetMetadata = "SetMetadata", |
||||
} |
||||
|
||||
export function ArtistWindowReducer(state: ArtistWindowState, action: any) { |
||||
switch (action.type) { |
||||
case ArtistWindowStateActions.SetMetadata: |
||||
return { ...state, metadata: action.value } |
||||
default: |
||||
throw new Error("Unimplemented ArtistWindow state update.") |
||||
} |
||||
} |
||||
|
||||
export interface IProps { |
||||
state: ArtistWindowState, |
||||
dispatch: (action: any) => void, |
||||
mainDispatch: (action: any) => void, |
||||
} |
||||
|
||||
export async function getArtistMetadata(id: number) { |
||||
const query = { |
||||
prop: serverApi.QueryElemProperty.artistId, |
||||
propOperand: id, |
||||
propOperator: serverApi.QueryFilterOp.Eq, |
||||
}; |
||||
|
||||
var q: serverApi.QueryRequest = { |
||||
query: query, |
||||
offsetsLimits: { |
||||
artistOffset: 0, |
||||
artistLimit: 1, |
||||
}, |
||||
ordering: { |
||||
orderBy: { |
||||
type: serverApi.OrderByType.Name, |
||||
}, |
||||
ascending: true, |
||||
}, |
||||
}; |
||||
|
||||
const requestOpts = { |
||||
method: 'POST', |
||||
headers: { 'Content-Type': 'application/json' }, |
||||
body: JSON.stringify(q), |
||||
}; |
||||
|
||||
return (async () => { |
||||
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) |
||||
let json: any = await response.json(); |
||||
let artist = json.artists[0]; |
||||
return { |
||||
name: artist.name |
||||
} |
||||
})(); |
||||
} |
||||
|
||||
export default function ArtistWindow(props: IProps) { |
||||
let metadata = props.state.metadata; |
||||
|
||||
useEffect(() => { |
||||
getArtistMetadata(props.state.artistId) |
||||
.then((m: ArtistMetadata) => { |
||||
console.log("metadata", m); |
||||
props.dispatch({ |
||||
type: ArtistWindowStateActions.SetMetadata, |
||||
value: m |
||||
}); |
||||
}) |
||||
}, [props.state.metadata?.name]); |
||||
|
||||
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
||||
<Box |
||||
m={1} |
||||
mt={4} |
||||
width="80%" |
||||
> |
||||
<PersonIcon style={{ fontSize: 80 }}/> |
||||
</Box> |
||||
<Box |
||||
m={1} |
||||
width="80%" |
||||
> |
||||
{metadata && <Typography variant="h4">{metadata.name}</Typography>} |
||||
</Box> |
||||
</Box> |
||||
} |
@ -0,0 +1,101 @@ |
||||
import React, { useEffect } from 'react'; |
||||
import { Box, Typography } from '@material-ui/core'; |
||||
import AudiotrackIcon from '@material-ui/icons/Audiotrack'; |
||||
import * as serverApi from '../../api'; |
||||
import { WindowState } from './Windows'; |
||||
|
||||
export interface SongMetadata { |
||||
title: string, |
||||
} |
||||
|
||||
export interface SongWindowState extends WindowState { |
||||
songId: number, |
||||
metadata: SongMetadata | null, |
||||
} |
||||
|
||||
export enum SongWindowStateActions { |
||||
SetMetadata = "SetMetadata", |
||||
} |
||||
|
||||
export function SongWindowReducer(state: SongWindowState, action: any) { |
||||
switch (action.type) { |
||||
case SongWindowStateActions.SetMetadata: |
||||
return { ...state, metadata: action.value } |
||||
default: |
||||
throw new Error("Unimplemented SongWindow state update.") |
||||
} |
||||
} |
||||
|
||||
export interface IProps { |
||||
state: SongWindowState, |
||||
dispatch: (action: any) => void, |
||||
mainDispatch: (action: any) => void, |
||||
} |
||||
|
||||
export async function getSongMetadata(id: number) { |
||||
const query = { |
||||
prop: serverApi.QueryElemProperty.songId, |
||||
propOperand: id, |
||||
propOperator: serverApi.QueryFilterOp.Eq, |
||||
}; |
||||
|
||||
var q: serverApi.QueryRequest = { |
||||
query: query, |
||||
offsetsLimits: { |
||||
songOffset: 0, |
||||
songLimit: 1, |
||||
}, |
||||
ordering: { |
||||
orderBy: { |
||||
type: serverApi.OrderByType.Name, |
||||
}, |
||||
ascending: true, |
||||
}, |
||||
}; |
||||
|
||||
const requestOpts = { |
||||
method: 'POST', |
||||
headers: { 'Content-Type': 'application/json' }, |
||||
body: JSON.stringify(q), |
||||
}; |
||||
|
||||
return (async () => { |
||||
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) |
||||
let json: any = await response.json(); |
||||
let song = json.songs[0]; |
||||
return { |
||||
title: song.title |
||||
} |
||||
})(); |
||||
} |
||||
|
||||
export default function SongWindow(props: IProps) { |
||||
let metadata = props.state.metadata; |
||||
|
||||
useEffect(() => { |
||||
getSongMetadata(props.state.songId) |
||||
.then((m: SongMetadata) => { |
||||
console.log("metadata", m); |
||||
props.dispatch({ |
||||
type: SongWindowStateActions.SetMetadata, |
||||
value: m |
||||
}); |
||||
}) |
||||
}, [props.state.metadata?.title]); |
||||
|
||||
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
||||
<Box |
||||
m={1} |
||||
mt={4} |
||||
width="80%" |
||||
> |
||||
<AudiotrackIcon style={{ fontSize: 80 }}/> |
||||
</Box> |
||||
<Box |
||||
m={1} |
||||
width="80%" |
||||
> |
||||
{metadata && <Typography variant="h4">{metadata.title}</Typography>} |
||||
</Box> |
||||
</Box> |
||||
} |
@ -0,0 +1,101 @@ |
||||
import React, { useEffect } from 'react'; |
||||
import { Box, Typography } from '@material-ui/core'; |
||||
import LocalOfferIcon from '@material-ui/icons/LocalOffer'; |
||||
import * as serverApi from '../../api'; |
||||
import { WindowState } from './Windows'; |
||||
|
||||
export interface TagMetadata { |
||||
name: string, |
||||
} |
||||
|
||||
export interface TagWindowState extends WindowState { |
||||
tagId: number, |
||||
metadata: TagMetadata | null, |
||||
} |
||||
|
||||
export enum TagWindowStateActions { |
||||
SetMetadata = "SetMetadata", |
||||
} |
||||
|
||||
export function TagWindowReducer(state: TagWindowState, action: any) { |
||||
switch (action.type) { |
||||
case TagWindowStateActions.SetMetadata: |
||||
return { ...state, metadata: action.value } |
||||
default: |
||||
throw new Error("Unimplemented TagWindow state update.") |
||||
} |
||||
} |
||||
|
||||
export interface IProps { |
||||
state: TagWindowState, |
||||
dispatch: (action: any) => void, |
||||
mainDispatch: (action: any) => void, |
||||
} |
||||
|
||||
export async function getTagMetadata(id: number) { |
||||
const query = { |
||||
prop: serverApi.QueryElemProperty.tagId, |
||||
propOperand: id, |
||||
propOperator: serverApi.QueryFilterOp.Eq, |
||||
}; |
||||
|
||||
var q: serverApi.QueryRequest = { |
||||
query: query, |
||||
offsetsLimits: { |
||||
tagOffset: 0, |
||||
tagLimit: 1, |
||||
}, |
||||
ordering: { |
||||
orderBy: { |
||||
type: serverApi.OrderByType.Name, |
||||
}, |
||||
ascending: true, |
||||
}, |
||||
}; |
||||
|
||||
const requestOpts = { |
||||
method: 'POST', |
||||
headers: { 'Content-Type': 'application/json' }, |
||||
body: JSON.stringify(q), |
||||
}; |
||||
|
||||
return (async () => { |
||||
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) |
||||
let json: any = await response.json(); |
||||
let tag = json.tags[0]; |
||||
return { |
||||
name: tag.name |
||||
} |
||||
})(); |
||||
} |
||||
|
||||
export default function TagWindow(props: IProps) { |
||||
let metadata = props.state.metadata; |
||||
|
||||
useEffect(() => { |
||||
getTagMetadata(props.state.tagId) |
||||
.then((m: TagMetadata) => { |
||||
console.log("metadata", m); |
||||
props.dispatch({ |
||||
type: TagWindowStateActions.SetMetadata, |
||||
value: m |
||||
}); |
||||
}) |
||||
}, [props.state.metadata?.name]); |
||||
|
||||
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
||||
<Box |
||||
m={1} |
||||
mt={4} |
||||
width="80%" |
||||
> |
||||
<LocalOfferIcon style={{ fontSize: 80 }}/> |
||||
</Box> |
||||
<Box |
||||
m={1} |
||||
width="80%" |
||||
> |
||||
{metadata && <Typography variant="h4">{metadata.name}</Typography>} |
||||
</Box> |
||||
</Box> |
||||
} |
@ -0,0 +1,70 @@ |
||||
import React from 'react'; |
||||
import { QueryWindowReducer } from "./QueryWindow"; |
||||
import { ArtistWindowReducer } from "./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 { SongWindowReducer } from './SongWindow'; |
||||
import { AlbumWindowReducer } from './AlbumWindow'; |
||||
import { TagWindowReducer } from './TagWindow'; |
||||
|
||||
export enum WindowType { |
||||
Query = "Query", |
||||
Artist = "Artist", |
||||
Album = "Album", |
||||
Tag = "Tag", |
||||
Song = "Song", |
||||
} |
||||
|
||||
export interface WindowState { |
||||
tabLabel: string, |
||||
} |
||||
|
||||
export const newWindowReducer = { |
||||
[WindowType.Query]: QueryWindowReducer, |
||||
[WindowType.Artist]: ArtistWindowReducer, |
||||
[WindowType.Album]: AlbumWindowReducer, |
||||
[WindowType.Song]: SongWindowReducer, |
||||
[WindowType.Tag]: TagWindowReducer, |
||||
} |
||||
|
||||
export const newWindowState = { |
||||
[WindowType.Query]: () => { |
||||
return { |
||||
tabLabel: <><SearchIcon/>Query</>, |
||||
editingQuery: false, |
||||
query: null, |
||||
resultsForQuery: null, |
||||
}; |
||||
}, |
||||
[WindowType.Artist]: () => { |
||||
return { |
||||
tabLabel: <><PersonIcon/>Artist</>, |
||||
artistId: 1, |
||||
metadata: null, |
||||
} |
||||
}, |
||||
[WindowType.Album]: () => { |
||||
return { |
||||
tabLabel: <><AlbumIcon/>Album</>, |
||||
albumId: 1, |
||||
metadata: null, |
||||
} |
||||
}, |
||||
[WindowType.Song]: () => { |
||||
return { |
||||
tabLabel: <><AudiotrackIcon/>Song</>, |
||||
songId: 1, |
||||
metadata: null, |
||||
} |
||||
}, |
||||
[WindowType.Tag]: () => { |
||||
return { |
||||
tabLabel: <><LocalOfferIcon/>Tag</>, |
||||
tagId: 1, |
||||
metadata: null, |
||||
} |
||||
}, |
||||
} |
Loading…
Reference in new issue