BREAKING CHANGE: feat: fix: ll
This commit is contained in:
parent
0f101b92e0
commit
36d689e2b6
|
|
@ -38,6 +38,7 @@ export default function AddPlaylistButton({ onCreate }: AddPlaylistButtonProps)
|
|||
<TextField
|
||||
name='playlistName'
|
||||
label='Playlist name'
|
||||
autoComplete='off'
|
||||
fullWidth
|
||||
/>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import React from "react";
|
||||
import { MotionBox } from "./MotionComponents";
|
||||
import { MusicNote } from "@mui/icons-material";
|
||||
|
||||
type PlaylistCardProps = {
|
||||
name: string;
|
||||
onClick?: () => void;
|
||||
};
|
||||
export default function PlaylistCard({ name, onClick }: PlaylistCardProps) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
display='flex'
|
||||
flexDirection='column'
|
||||
justifyContent='center'
|
||||
onClick={onClick}
|
||||
>
|
||||
<MotionBox
|
||||
width={120}
|
||||
height={120}
|
||||
display='flex'
|
||||
justifyContent='center'
|
||||
alignContent='center'
|
||||
alignItems='center'
|
||||
bgcolor='primary.main'
|
||||
color='success.contrastText'
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
whileHover={{ borderRadius: '30%', scale: 0.8 }}
|
||||
whileTap={{ borderRadius: '40%', scale: 0.7, backgroundColor: theme.palette.info.main, rotate: '90deg' }}
|
||||
>
|
||||
<MusicNote sx={{ scale: 2 }} />
|
||||
</MotionBox>
|
||||
<Typography textAlign='center'>
|
||||
New playlist
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,25 +1,50 @@
|
|||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import LoadingCard from "../components/LoadingCard";
|
||||
import Scroll from "../components/Scroll";
|
||||
import Page, { PageRoot } from "../components/Page";
|
||||
import AppHeader from "../components/AppHeader";
|
||||
import AddPlaylistButton from "../components/AddPlaylistButton";
|
||||
import { createPlaylist, getPlaylists, Playlist } from "../utils/MusicServer";
|
||||
import useForceUpdate from "../hooks/useForceUpdate";
|
||||
import PlaylistCard from "../components/PlaylistCard";
|
||||
import useQueue from "../hooks/useQueue";
|
||||
|
||||
export default function Landing() {
|
||||
const theme = useTheme();
|
||||
const queue = useQueue();
|
||||
const [playlists, setPlaylists] = useState<Playlist[]>([]);
|
||||
|
||||
const updatePlaylists = () => {
|
||||
getPlaylists().then(setPlaylists);
|
||||
};
|
||||
|
||||
const handleCreatePlaylist = (name: string) => {
|
||||
console.log(`Creating playlist ${name}`);
|
||||
createPlaylist(name);
|
||||
updatePlaylists();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updatePlaylists();
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<AppHeader />
|
||||
<PageRoot>
|
||||
|
||||
<Typography variant='button'>Playlists</Typography>
|
||||
<Scroll display='flex' gap={theme.spacing(2)}>
|
||||
{playlists.map(
|
||||
playlist => (
|
||||
<PlaylistCard
|
||||
name={playlist.name}
|
||||
onClick={() => {
|
||||
if (playlist.songs.length === 0) return alert('There are no any songs in this playlist');
|
||||
queue?.setSongs(playlist.songs);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<AddPlaylistButton onCreate={handleCreatePlaylist} />
|
||||
</Scroll>
|
||||
</PageRoot>
|
||||
|
|
|
|||
|
|
@ -3,17 +3,19 @@ import { PageRoot } from "../components/Page";
|
|||
import AppHeader from "../components/AppHeader";
|
||||
import repeatArray from "../utils/repeatArray";
|
||||
import { LoadingSearchSongCard } from "../components/LoadingCard";
|
||||
import { saveSong, searchSongs } from "../utils/MusicServer";
|
||||
import { Box, IconButton, Snackbar } from "@mui/material";
|
||||
import { addSong, getPlaylists, Playlist, removeSong, saveSong, searchSongs, Song } from "../utils/MusicServer";
|
||||
import { Box, Checkbox, Dialog, DialogContent, DialogTitle, IconButton, List, ListItem, ListItemButton, ListItemText, Snackbar } from "@mui/material";
|
||||
import { SearchSongCard } from "../components/SongCard";
|
||||
import useQueue from "../hooks/useQueue";
|
||||
import { Close } from "@mui/icons-material";
|
||||
import { Check, Close, PlaylistAdd } from "@mui/icons-material";
|
||||
|
||||
export default function Search() {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [results, setResults] = useState<ReactNode[]>([]);
|
||||
const queue = useQueue();
|
||||
const [snackbar, setSnackbar] = useState<string | null>(null);
|
||||
const [addTo, setAddTo] = useState<Song | null>(null);
|
||||
const [playlists, setPlaylists] = useState<Playlist[]>([]);
|
||||
const onKeyUp = async (evt: KeyboardEvent) => {
|
||||
if (evt.key !== 'Enter') return;
|
||||
const q = inputRef.current?.value;
|
||||
|
|
@ -35,6 +37,14 @@ export default function Search() {
|
|||
queue?.addSong(song);
|
||||
setSnackbar(`Added "${song.name}" to queue.`);
|
||||
}}
|
||||
controls={
|
||||
<IconButton onClick={() => {
|
||||
setAddTo(song);
|
||||
getPlaylists().then(setPlaylists);
|
||||
}}>
|
||||
<PlaylistAdd />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -43,6 +53,37 @@ export default function Search() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={addTo ? true : false} onClose={() => setAddTo(null)}>
|
||||
<DialogTitle>
|
||||
Add to playlist
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<List>
|
||||
{playlists.map(x => {
|
||||
const added = x.songs.find(y => y.videoId === addTo?.videoId) ? true : false;
|
||||
return (
|
||||
<ListItem>
|
||||
<Checkbox
|
||||
onClick={() => {
|
||||
if (!addTo) return;
|
||||
if (added) {
|
||||
removeSong(x.id, addTo);
|
||||
} else {
|
||||
addSong(x.id, addTo);
|
||||
setAddTo(null);
|
||||
}
|
||||
}}
|
||||
checked={added}
|
||||
/>
|
||||
<ListItemText>
|
||||
{x.name}
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<AppHeader
|
||||
showSearch
|
||||
inputProps={{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { saveAs } from 'file-saver';
|
||||
|
||||
const API_BASE = `http://localhost:33223`;
|
||||
const API_BASE = `https://music-api.extera.xyz`;
|
||||
|
||||
export type Artist = {
|
||||
name: string;
|
||||
|
|
@ -30,6 +30,8 @@ export type Song = {
|
|||
|
||||
export type Playlist = {
|
||||
owner: string;
|
||||
id: string;
|
||||
name: string;
|
||||
songs: Song[];
|
||||
};
|
||||
|
||||
|
|
@ -37,7 +39,8 @@ async function doPost(p: string, json: any) {
|
|||
const jsonString = JSON.stringify(json);
|
||||
const response = await fetch(API_BASE + p, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: localStorage.token ? `Bearer ${localStorage.token}` : ''
|
||||
},
|
||||
body: jsonString,
|
||||
method: 'POST'
|
||||
|
|
@ -47,14 +50,21 @@ async function doPost(p: string, json: any) {
|
|||
}
|
||||
|
||||
async function doGet(p: string) {
|
||||
const response = await fetch(API_BASE + p);
|
||||
const response = await fetch(API_BASE + p, {
|
||||
headers: {
|
||||
Authorization: localStorage.token ? `Bearer ${localStorage.token}` : ''
|
||||
}
|
||||
});
|
||||
if (!response.ok) throw await response.json();
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function doDelete(p: string) {
|
||||
const response = await fetch(API_BASE + p, {
|
||||
method: 'DELETE'
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: localStorage.token ? `Bearer ${localStorage.token}` : ''
|
||||
}
|
||||
});
|
||||
if (!response.ok) throw await response.json();
|
||||
return await response.json();
|
||||
|
|
|
|||
Loading…
Reference in New Issue