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
|
<TextField
|
||||||
name='playlistName'
|
name='playlistName'
|
||||||
label='Playlist name'
|
label='Playlist name'
|
||||||
|
autoComplete='off'
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</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 { Box, Typography, useTheme } from "@mui/material";
|
||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import LoadingCard from "../components/LoadingCard";
|
import LoadingCard from "../components/LoadingCard";
|
||||||
import Scroll from "../components/Scroll";
|
import Scroll from "../components/Scroll";
|
||||||
import Page, { PageRoot } from "../components/Page";
|
import Page, { PageRoot } from "../components/Page";
|
||||||
import AppHeader from "../components/AppHeader";
|
import AppHeader from "../components/AppHeader";
|
||||||
import AddPlaylistButton from "../components/AddPlaylistButton";
|
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() {
|
export default function Landing() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const queue = useQueue();
|
||||||
|
const [playlists, setPlaylists] = useState<Playlist[]>([]);
|
||||||
|
|
||||||
|
const updatePlaylists = () => {
|
||||||
|
getPlaylists().then(setPlaylists);
|
||||||
|
};
|
||||||
|
|
||||||
const handleCreatePlaylist = (name: string) => {
|
const handleCreatePlaylist = (name: string) => {
|
||||||
console.log(`Creating playlist ${name}`);
|
createPlaylist(name);
|
||||||
|
updatePlaylists();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updatePlaylists();
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<PageRoot>
|
<PageRoot>
|
||||||
|
|
||||||
<Typography variant='button'>Playlists</Typography>
|
<Typography variant='button'>Playlists</Typography>
|
||||||
<Scroll display='flex' gap={theme.spacing(2)}>
|
<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} />
|
<AddPlaylistButton onCreate={handleCreatePlaylist} />
|
||||||
</Scroll>
|
</Scroll>
|
||||||
</PageRoot>
|
</PageRoot>
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,19 @@ import { PageRoot } from "../components/Page";
|
||||||
import AppHeader from "../components/AppHeader";
|
import AppHeader from "../components/AppHeader";
|
||||||
import repeatArray from "../utils/repeatArray";
|
import repeatArray from "../utils/repeatArray";
|
||||||
import { LoadingSearchSongCard } from "../components/LoadingCard";
|
import { LoadingSearchSongCard } from "../components/LoadingCard";
|
||||||
import { saveSong, searchSongs } from "../utils/MusicServer";
|
import { addSong, getPlaylists, Playlist, removeSong, saveSong, searchSongs, Song } from "../utils/MusicServer";
|
||||||
import { Box, IconButton, Snackbar } from "@mui/material";
|
import { Box, Checkbox, Dialog, DialogContent, DialogTitle, IconButton, List, ListItem, ListItemButton, ListItemText, Snackbar } from "@mui/material";
|
||||||
import { SearchSongCard } from "../components/SongCard";
|
import { SearchSongCard } from "../components/SongCard";
|
||||||
import useQueue from "../hooks/useQueue";
|
import useQueue from "../hooks/useQueue";
|
||||||
import { Close } from "@mui/icons-material";
|
import { Check, Close, PlaylistAdd } from "@mui/icons-material";
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [results, setResults] = useState<ReactNode[]>([]);
|
const [results, setResults] = useState<ReactNode[]>([]);
|
||||||
const queue = useQueue();
|
const queue = useQueue();
|
||||||
const [snackbar, setSnackbar] = useState<string | null>(null);
|
const [snackbar, setSnackbar] = useState<string | null>(null);
|
||||||
|
const [addTo, setAddTo] = useState<Song | null>(null);
|
||||||
|
const [playlists, setPlaylists] = useState<Playlist[]>([]);
|
||||||
const onKeyUp = async (evt: KeyboardEvent) => {
|
const onKeyUp = async (evt: KeyboardEvent) => {
|
||||||
if (evt.key !== 'Enter') return;
|
if (evt.key !== 'Enter') return;
|
||||||
const q = inputRef.current?.value;
|
const q = inputRef.current?.value;
|
||||||
|
|
@ -35,6 +37,14 @@ export default function Search() {
|
||||||
queue?.addSong(song);
|
queue?.addSong(song);
|
||||||
setSnackbar(`Added "${song.name}" to queue.`);
|
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 (
|
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
|
<AppHeader
|
||||||
showSearch
|
showSearch
|
||||||
inputProps={{
|
inputProps={{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
const API_BASE = `http://localhost:33223`;
|
const API_BASE = `https://music-api.extera.xyz`;
|
||||||
|
|
||||||
export type Artist = {
|
export type Artist = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -30,6 +30,8 @@ export type Song = {
|
||||||
|
|
||||||
export type Playlist = {
|
export type Playlist = {
|
||||||
owner: string;
|
owner: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
songs: Song[];
|
songs: Song[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,7 +39,8 @@ async function doPost(p: string, json: any) {
|
||||||
const jsonString = JSON.stringify(json);
|
const jsonString = JSON.stringify(json);
|
||||||
const response = await fetch(API_BASE + p, {
|
const response = await fetch(API_BASE + p, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: localStorage.token ? `Bearer ${localStorage.token}` : ''
|
||||||
},
|
},
|
||||||
body: jsonString,
|
body: jsonString,
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
|
|
@ -47,14 +50,21 @@ async function doPost(p: string, json: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doGet(p: string) {
|
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();
|
if (!response.ok) throw await response.json();
|
||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doDelete(p: string) {
|
async function doDelete(p: string) {
|
||||||
const response = await fetch(API_BASE + p, {
|
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();
|
if (!response.ok) throw await response.json();
|
||||||
return await response.json();
|
return await response.json();
|
||||||
|
|
@ -70,11 +80,11 @@ export function getSongURL(song?: Song) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function register(username?: string, password?: string): Promise<string> {
|
export async function register(username?: string, password?: string): Promise<string> {
|
||||||
return (await doPost(`/auth/register`, {username, password})).token as string;
|
return (await doPost(`/auth/register`, { username, password })).token as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function login(username?: string, password?: string): Promise<string> {
|
export async function login(username?: string, password?: string): Promise<string> {
|
||||||
return (await doPost(`/auth/login`, {username, password})).token as string;
|
return (await doPost(`/auth/login`, { username, password })).token as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLyrics(song: Song): Promise<string[]> {
|
export async function getLyrics(song: Song): Promise<string[]> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue