BREAKING CHANGE: feat: fix: ll

This commit is contained in:
OfficialDakari 2024-10-18 21:58:47 +05:00
parent 0f101b92e0
commit 36d689e2b6
5 changed files with 128 additions and 12 deletions

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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={{

View File

@ -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[]> {