коммит на всякий случай
This commit is contained in:
parent
ee17998948
commit
2fa22ec4c4
2
.env
2
.env
@ -1,2 +1,2 @@
|
||||
REACT_APP_DATABASE='http://localhost:3000/'
|
||||
REACT_APP_DATABASE='http://localhost:3001/'
|
||||
REACT_APP_RECAPTCHA_SITE_KEY='6LcZ-kkqAAAAAFdmy2tD1gKFdjxb0D71w5VMaisr'
|
18
README.md
18
README.md
@ -1,18 +1,6 @@
|
||||
# PromoCursed
|
||||
## Курсовая работа
|
||||
|
||||
ВАЖНАЯ ИНФОРМАЦИЯ!!!
|
||||
### Описание
|
||||
|
||||
По какой-то причине при передаче объекта песни в CurrentTrack и получение свойств объекта в этом компонента
|
||||
появляются ошибки. Их не было до какого-то момента, но я не успел к сроку выявить причины (я долго дебажил),
|
||||
но результата это не принесло. До сих пор причины для меня остаются, к сожалению, загадкой...
|
||||
|
||||
Что было добавлено/исправлено после очной демонстрации:
|
||||
|
||||
1. Компоненты были раскиданы по папкам
|
||||
2. Переименовал некоторые папки (например, API),
|
||||
3. Добавил новые компоненты
|
||||
4. Роутинг и страницы
|
||||
5. Валидация форм
|
||||
|
||||
Должен был добавить плеер с воспроизведением песен, но из-за ошибок, про которые я написал выше, не удалось.
|
||||
Пришлось жестко передать объект песни прямо в коде компонента.
|
||||
Веб-приложение для прослушивания музыки.
|
||||
|
75
data.json
75
data.json
@ -7,7 +7,7 @@
|
||||
"band_name": "Nevroz",
|
||||
"albumid": "1",
|
||||
"album_name": "Album 1",
|
||||
"source": "/src/songs/Nirvana_-_Smells_Like_Teen_Spirit_75941061.mp3",
|
||||
"source": "http://localhost:3001/songs_sources/nirvana.mp3",
|
||||
"cover": "https://cdn1.ozone.ru/s3/multimedia-t/6893834213.jpg",
|
||||
"playlists": [
|
||||
"1",
|
||||
@ -23,8 +23,8 @@
|
||||
"band_name": "Nevroz",
|
||||
"albumid": "1",
|
||||
"album_name": "Album 1",
|
||||
"source": "/src/songs/Blur_-_Song_2_47967381.mp3",
|
||||
"cover": "https://cdn1.ozone.ru/s3/multimedia-t/6893834213.jpg",
|
||||
"source": "http://localhost:3001/songs_sources/blur.mp3",
|
||||
"cover": "https://lastfm.freetls.fastly.net/i/u/ar0/101e39a435244b05cbf9d3af6ddf8c74.jpg",
|
||||
"playlists": [
|
||||
"2"
|
||||
],
|
||||
@ -38,7 +38,7 @@
|
||||
"band_name": "noizemchik",
|
||||
"albumid": "2",
|
||||
"album_name": "Ругань из-за Стёпы",
|
||||
"source": "/src/songs/Noice_MC_-_Rugan_iz-za_steny_63872179.mp3",
|
||||
"source": "http://localhost:3001/songs_sources/noizemc.mp3",
|
||||
"cover": "https://sun9-11.userapi.com/impg/yfCwuWXI6NkFVC2HvMlegM2qWLlenkeiiRyvNQ/jpH8m1wlLqs.jpg?size=604x604&quality=95&sign=60a1d1877b49631fb6078db88715fc88&c_uniq_tag=UKrig2vg02reyBH_ML0OKeqt5gm_2jpwEXD7NJBUXoQ&type=album"
|
||||
,
|
||||
"playlists": [
|
||||
@ -49,15 +49,15 @@
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"song_name": "Crazy Frog",
|
||||
"band_id": "3",
|
||||
"song_name": "Хот вилз",
|
||||
"band_id": "1",
|
||||
"band_name": "DSPD",
|
||||
"albumid": "3",
|
||||
"album_name": "Album 3",
|
||||
"source": "/src/songs/DSPD_-_Crazy_Frog_75801748.mp3",
|
||||
"albumid": "1",
|
||||
"album_name": "Album 1",
|
||||
"source": "http://localhost:3001/songs_sources/hotweelz.mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/10129881/f3cf1afc.a.30561322-1/m1000x1000?webp=falseh",
|
||||
"playlists": [
|
||||
"2", "3"
|
||||
"1", "2"
|
||||
],
|
||||
"genreid": "1",
|
||||
"genre_name": "Rock"
|
||||
@ -69,8 +69,8 @@
|
||||
"band_name": "DSPD",
|
||||
"albumid": "3",
|
||||
"album_name": "Album 3",
|
||||
"source": "/src/songs/DSPD feat. даня хренников - Кем я стал_(audio-lord.ru).mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/10129881/f3cf1afc.a.30561322-1/m1000x1000?webp=falseh",
|
||||
"source": "http://localhost:3001/songs_sources/kemstal.mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/6058982/7e431a83.a.23910092-1/m1000x1000?webp=false",
|
||||
"playlists": [
|
||||
"3"
|
||||
],
|
||||
@ -79,28 +79,29 @@
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"song_name": "Smells Like Poop",
|
||||
"band_id": "1",
|
||||
"band_name": "Nevroz",
|
||||
"albumid": "1",
|
||||
"album_name": "Album 1",
|
||||
"source": "/src/songs/Nirvana_-_Smells_Like_Teen_Spirit_75941061.mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/10129881/f3cf1afc.a.30561322-1/m1000x1000?webp=falseh",
|
||||
"song_name": "American Idiotus",
|
||||
"band_id": "3",
|
||||
"band_name": "Red Day",
|
||||
"albumid": "3",
|
||||
"album_name": "Album 3",
|
||||
"source": "http://localhost:3001/songs_sources/greenday.mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/32236/d3846188.a.1001691-1/m1000x1000?webp=false",
|
||||
"playlists": [
|
||||
"1", "2"
|
||||
"2", "3"
|
||||
],
|
||||
"genreid": "1",
|
||||
"genre_name": "Rock"
|
||||
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"song_name": "Song 3",
|
||||
"song_name": "Ulyanofication'",
|
||||
"band_id": "1",
|
||||
"band_name": "Nevroz",
|
||||
"band_name": "Yellow Warm Russian Tomatoes",
|
||||
"albumid": "1",
|
||||
"album_name": "Album 1",
|
||||
"source": "/src/songs/Blur_-_Song_2_47967381.mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/10129881/f3cf1afc.a.30561322-1/m1000x1000?webp=falseh",
|
||||
"source": "http://localhost:3001/songs_sources/pepers.mp3",
|
||||
"cover": "https://avatars.mds.yandex.net/i?id=8d01018aa8cac0da9845314da42e050ab8e61713-12569754-images-thumbs&n=13",
|
||||
"playlists": [
|
||||
"2"
|
||||
],
|
||||
@ -109,13 +110,13 @@
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"song_name": "Ругань из-за Стёпы",
|
||||
"song_name": "СУМАСШЕДШИЙ ПОЕЗД!!!!",
|
||||
"band_id": "2",
|
||||
"band_name": "noizemchik",
|
||||
"band_name": "СТАРИНА ОЗЗИ ОЗБОРН (МУЗЫКА ДЬЯВОЛА)",
|
||||
"albumid": "2",
|
||||
"album_name": "Ругань из-за Стёпы",
|
||||
"source": "/src/songs/Noice_MC_-_Rugan_iz-za_steny_63872179.mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/10129881/f3cf1afc.a.30561322-1/m1000x1000?webp=falseh"
|
||||
"source": "http://localhost:3001/songs_sources/ozzy.mp3",
|
||||
"cover": "https://avatars.mds.yandex.net/i?id=95adb2fda305e542621b7d9757d70ddd_l-4937470-images-thumbs&n=13"
|
||||
,
|
||||
"playlists": [
|
||||
"1", "3"
|
||||
@ -125,13 +126,13 @@
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"song_name": "Crazy Frog",
|
||||
"song_name": "Downstairs to Heaven",
|
||||
"band_id": "3",
|
||||
"band_name": "DSPD",
|
||||
"band_name": "Эйси Диси",
|
||||
"albumid": "3",
|
||||
"album_name": "Album 3",
|
||||
"source": "/src/songs/DSPD_-_Crazy_Frog_75801748.mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/10129881/f3cf1afc.a.30561322-1/m1000x1000?webp=falseh",
|
||||
"source": "http://localhost:3001/songs_sources/acdc.mp3",
|
||||
"cover": "https://avatars.mds.yandex.net/get-entity_search/509339/292583724/S600xU",
|
||||
"playlists": [
|
||||
"2", "3"
|
||||
],
|
||||
@ -140,13 +141,13 @@
|
||||
},
|
||||
{
|
||||
"id": "10",
|
||||
"song_name": "Кем я стал",
|
||||
"song_name": "Ай воз мэйд фор лавин ю беби",
|
||||
"band_id": "3",
|
||||
"band_name": "DSPD",
|
||||
"band_name": "KISS",
|
||||
"albumid": "3",
|
||||
"album_name": "Album 3",
|
||||
"source": "/src/songs/DSPD feat. даня хренников - Кем я стал_(audio-lord.ru).mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/10129881/f3cf1afc.a.30561322-1/m1000x1000?webp=falseh",
|
||||
"source": "http://localhost:3001/songs_sources/kiss.mp3",
|
||||
"cover": "https://avatars.dzeninfra.ru/get-zen_doc/10073791/pub_64ab1cf9ec43747a7a73a3e0_64ab1d726ba4132c8f8b37b6/scale_1200",
|
||||
"playlists": [
|
||||
"3"
|
||||
],
|
||||
@ -160,7 +161,7 @@
|
||||
"band_name": "DSPD",
|
||||
"albumid": "3",
|
||||
"album_name": "Album 3",
|
||||
"source": "/src/songs/DSPD feat. даня хренников - Кем я стал_(audio-lord.ru).mp3",
|
||||
"source": "http://localhost:3001/songs_sources/kemstal.mp3",
|
||||
"cover": "https://avatars.yandex.net/get-music-content/10129881/f3cf1afc.a.30561322-1/m1000x1000?webp=falseh",
|
||||
"playlists": [
|
||||
"3"
|
||||
|
@ -33,7 +33,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-dotenv && set port=5000 && react-scripts start",
|
||||
"json-server": "npx json-server data.json --port 3000",
|
||||
"json-server": "npx json-server data.json --port 3001 --static ./public/songs_sources",
|
||||
"build": "react-dotenv && react-scripts build",
|
||||
"test": "react-dotenv && react-scripts test",
|
||||
"eject": "react-dotenv && react-scripts eject",
|
||||
|
BIN
public/songs_sources/acdc.mp3
Normal file
BIN
public/songs_sources/acdc.mp3
Normal file
Binary file not shown.
BIN
public/songs_sources/greenday.mp3
Normal file
BIN
public/songs_sources/greenday.mp3
Normal file
Binary file not shown.
BIN
public/songs_sources/hotweelz.mp3
Normal file
BIN
public/songs_sources/hotweelz.mp3
Normal file
Binary file not shown.
BIN
public/songs_sources/kiss.mp3
Normal file
BIN
public/songs_sources/kiss.mp3
Normal file
Binary file not shown.
BIN
public/songs_sources/ozzy.mp3
Normal file
BIN
public/songs_sources/ozzy.mp3
Normal file
Binary file not shown.
BIN
public/songs_sources/pepers.mp3
Normal file
BIN
public/songs_sources/pepers.mp3
Normal file
Binary file not shown.
@ -27,7 +27,7 @@ export async function getSongs() {
|
||||
|
||||
export async function getSong(id: string) {
|
||||
|
||||
return await axios.get<IPlaylist[]>(`${localhost}songs/` + id);
|
||||
return await axios.get<ISong>(`${localhost}songs/` + id);
|
||||
|
||||
}
|
||||
|
||||
|
34
src/App.css
34
src/App.css
@ -77,7 +77,12 @@
|
||||
-webkit-box-shadow: 0px -26px 19px 0px rgba(34, 60, 80, 0.2);
|
||||
-moz-box-shadow: 0px -26px 19px 0px rgba(34, 60, 80, 0.2);
|
||||
box-shadow: 0px -26px 19px 0px rgba(34, 60, 80, 0.2);
|
||||
|
||||
transform-origin: bottom center;
|
||||
animation: fadeInAndMoveUp 0.2s ease-out forwards;
|
||||
}
|
||||
@keyframes fadeInAndMoveUp {
|
||||
from { opacity: 0; transform: translateY(50px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.song-cover {
|
||||
@ -92,20 +97,40 @@
|
||||
.song-table-row {
|
||||
margin-top: 50px !important;
|
||||
margin-bottom: 50px !important;
|
||||
padding: 30px !important;
|
||||
}
|
||||
|
||||
.song-table-row:hover {
|
||||
outline: #9d0000 solid 3px !important;
|
||||
outline-offset: -1px !important;
|
||||
outline-offset: -2px !important;
|
||||
color: #7c0000 !important;
|
||||
background-color: #fcfcfc !important;
|
||||
}
|
||||
|
||||
.song-table-row-active {
|
||||
outline-offset: -2px !important;
|
||||
|
||||
background-color: #e7e7e7 !important;
|
||||
}
|
||||
|
||||
.ant-table-tbody {
|
||||
padding: 40 !important;
|
||||
}
|
||||
.ant-table-tbody > tr > td {
|
||||
padding: 40 !important;
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr {
|
||||
padding-top: 20px !important;
|
||||
padding-bottom: 20px !important;
|
||||
}
|
||||
|
||||
|
||||
.ant-table-cell:nth-child(1) {
|
||||
padding: 2px !important;
|
||||
padding: 1px !important;
|
||||
}
|
||||
|
||||
.play-song-button {
|
||||
@ -142,6 +167,7 @@
|
||||
color: grey;
|
||||
font-size: 30px !important;
|
||||
padding-right: 5px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.current-track-button {
|
||||
|
24
src/App.tsx
24
src/App.tsx
@ -7,21 +7,29 @@ import { Loginpage } from './pages/Loginpage';
|
||||
import { Registerpage } from './pages/Registerpage';
|
||||
import { Profilepage } from './pages/Profilepage';
|
||||
import { Layout } from './components/layoutComponents/Layout';
|
||||
import { CurrentSongProvider } from './contexts/SongContexts/SongContextProvider';
|
||||
import { PlayingProvider } from './contexts/SongContexts/PlayingProvider';
|
||||
import { VolumeProvider } from './contexts/VolumeContexts/VolumeProvider';
|
||||
|
||||
function App() {
|
||||
|
||||
return (
|
||||
|
||||
<div className="App bg-slate-100">
|
||||
<div id="app" className='bg-white'>
|
||||
<Routes>
|
||||
<Route path='/' element={<Layout />}>
|
||||
<Route index element={<Homepage song_id='2'/>} />
|
||||
<Route path='login' element={<Loginpage />} />
|
||||
<Route path='profile' element={<Profilepage />} />
|
||||
</Route>
|
||||
<CurrentSongProvider songId=''>
|
||||
<PlayingProvider isPlaying={false}>
|
||||
<VolumeProvider volumeValue={30}>
|
||||
<Routes>
|
||||
<Route path='/' element={<Layout />}>
|
||||
<Route index element={<Homepage />} />
|
||||
<Route path='login' element={<Loginpage />} />
|
||||
<Route path='profile' element={<Profilepage />} />
|
||||
</Route>
|
||||
|
||||
</Routes>
|
||||
</Routes>
|
||||
</VolumeProvider>
|
||||
</PlayingProvider>
|
||||
</CurrentSongProvider>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -8,10 +8,16 @@ import { AdBlock } from './AdBlock';
|
||||
import { Footer } from './Footer';
|
||||
import { Header } from './Header';
|
||||
import { MenuBlock } from './MenuBlock';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { usePlayingContext } from '../../contexts/SongContexts/PlayingProvider';
|
||||
|
||||
|
||||
export function Layout() {
|
||||
|
||||
return (
|
||||
<><Header />
|
||||
|
||||
<>
|
||||
<Header />
|
||||
<div id='main-content'>
|
||||
<Outlet />
|
||||
</div>
|
||||
|
@ -17,6 +17,7 @@ interface MenuBlockProps {
|
||||
}
|
||||
|
||||
export function MenuBlock({playlists, songs, albums, genres}: MenuBlockProps) {
|
||||
|
||||
const newSongsTab = {
|
||||
label: 'Новинки',
|
||||
key: 'New',
|
||||
@ -39,7 +40,8 @@ export function MenuBlock({playlists, songs, albums, genres}: MenuBlockProps) {
|
||||
key: 'Recomendations',
|
||||
children: songs? <NewSongsBlock /> : null
|
||||
};
|
||||
const tabs = [recTab, newSongsTab, chartTab, playlistsTab];
|
||||
const tabs = [recTab, newSongsTab, chartTab];
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex" style={{display: 'flex', alignSelf: 'flex-start', width: 'fit-content'}}>
|
||||
|
@ -7,7 +7,7 @@ import { PlayCircleFilled, PlayCircleOutlined } from '@ant-design/icons';
|
||||
import { getSongs } from '../../API/api';
|
||||
import Title from 'antd/es/typography/Title';
|
||||
import { GetColumns } from './Templates/songsTemplate';
|
||||
import { getColumnsWithNumber } from './Templates/numeredSongsTemplate';
|
||||
import { GetColumnsWithNumber } from './Templates/numeredSongsTemplate';
|
||||
|
||||
// Функция для генерации номеров строк
|
||||
const generateRowNumbers = (rows: any[]) =>
|
||||
@ -30,16 +30,19 @@ export function ChartSongsBlock() {
|
||||
return (
|
||||
<div className="song-block flex border-slate-100">
|
||||
<Table
|
||||
title={() => <Title level={4}>Чарт</Title>}
|
||||
style={{ width: '100%', backgroundColor: 'transparent' }}
|
||||
title={() => <Title style={{textAlign: 'center'}} level={4}>Чарт</Title>}
|
||||
style={{ width: '100%', backgroundColor: 'transparent', overflowX: 'auto' }}
|
||||
className="songs-table menu-block"
|
||||
dataSource={numberedSongs}
|
||||
columns={getColumnsWithNumber(numberedSongs)}
|
||||
columns={GetColumnsWithNumber(numberedSongs)}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
rowClassName={() => 'song-table-row'}
|
||||
rowClassName={() => 'song-table-row chart-row'}
|
||||
rowKey={(record) => record.id}
|
||||
scroll={{ x: true }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
@ -3,16 +3,21 @@ import { render } from '@testing-library/react';
|
||||
import {ISong} from '../../models/IModels';
|
||||
import axios from 'axios';
|
||||
import { Table, Button, Empty } from 'antd';
|
||||
import { PlayCircleFilled, PlayCircleOutlined } from '@ant-design/icons';
|
||||
import { Loading3QuartersOutlined, PlayCircleFilled, PlayCircleOutlined } from '@ant-design/icons';
|
||||
import { getSongs } from '../../API/api';
|
||||
import Title from 'antd/es/typography/Title';
|
||||
import { GetColumns } from './Templates/songsTemplate';
|
||||
|
||||
import { Spin } from "antd";
|
||||
import { useCurrentSongContext } from '../../contexts/SongContexts/SongContextProvider';
|
||||
import { usePlayingContext } from '../../contexts/SongContexts/PlayingProvider';
|
||||
|
||||
export function NewSongsBlock() {
|
||||
|
||||
const [songs, setSongs] = useState<ISong[]>([]);
|
||||
|
||||
const currentSongId = useCurrentSongContext();
|
||||
const isPlaying = usePlayingContext();
|
||||
|
||||
const fetchData = async () => {
|
||||
const response = getSongs();
|
||||
setSongs((await response).data);
|
||||
@ -25,17 +30,20 @@ export function NewSongsBlock() {
|
||||
return (
|
||||
<div className="song-block flex border-slate-100">
|
||||
|
||||
<Table
|
||||
title={() => <Title level={4}>Горячие новинки</Title>}
|
||||
style={{width: '100%', backgroundColor: 'transparent'}}
|
||||
className="songs-table menu-block"
|
||||
dataSource={songs.map((s: ISong) => ({...s, play: ''}))}
|
||||
columns={GetColumns(songs)}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
rowClassName={() => 'song-table-row'}
|
||||
rowKey={(s: ISong) => s.id}
|
||||
/>
|
||||
<Spin indicator={<Loading3QuartersOutlined spin style={{color: '#9d0000', fontSize: 30}} />} spinning={songs.length === 0}>
|
||||
<Table
|
||||
title={() => <Title level={4}>Горячие новинки</Title>}
|
||||
style={{width: '100%', backgroundColor: 'transparent'}}
|
||||
className="songs-table menu-block"
|
||||
dataSource={songs.map((s: ISong) => ({...s, play: ''}))}
|
||||
columns={GetColumns(songs)}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
rowClassName={(record) => isPlaying ? (record.id === currentSongId.songId ? 'song-table-row-active' : 'song-table-row') : 'song-table-row'}
|
||||
rowKey={(s: ISong) => s.id}
|
||||
rowHoverable={false}
|
||||
/>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,8 +1,18 @@
|
||||
import { Button } from "antd";
|
||||
import { ISong } from "../../../models/IModels";
|
||||
import PlayCircleFilled from "@ant-design/icons/lib/icons/PlayCircleFilled";
|
||||
import { useState } from "react";
|
||||
import { useCurrentSongContext } from "../../../contexts/SongContexts/SongContextProvider";
|
||||
import { usePlayingContext } from "../../../contexts/SongContexts/PlayingProvider";
|
||||
|
||||
export function getColumnsWithNumber(songs: ISong[]) {
|
||||
const useColumnsWithNumber = (songs: ISong[]) => {
|
||||
|
||||
const {isPlaying, setIsPlaying} = usePlayingContext();
|
||||
const { songId, setSongId } = useCurrentSongContext();
|
||||
|
||||
const handlePlayClick = (songId: string) => {
|
||||
setIsPlaying(!isPlaying);
|
||||
setSongId(songId);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
@ -23,21 +33,23 @@ export function getColumnsWithNumber(songs: ISong[]) {
|
||||
width: 100,
|
||||
|
||||
render: (text: string, song: ISong) => (
|
||||
<Button data-content="▷" className='play-song-button rounded' style={{background: 'transparent', display: 'contents'}}>
|
||||
<Button data-content={!isPlaying && song.id === songId ? '┃┃' : "▷"}
|
||||
className='play-song-button rounded' style={{background: 'transparent', display: 'contents'}}
|
||||
onClick={() => handlePlayClick(song.id)}>
|
||||
|
||||
<img
|
||||
className="rounded "
|
||||
src={song.cover}
|
||||
alt={song.song_name}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'song_name',
|
||||
key: 'song_name',
|
||||
width: 350
|
||||
width: 400
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
@ -49,8 +61,12 @@ export function getColumnsWithNumber(songs: ISong[]) {
|
||||
title: '',
|
||||
dataIndex: 'album_name',
|
||||
key: 'album_name',
|
||||
width: 400
|
||||
width: 350
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
export function GetColumnsWithNumber(songs: ISong[]) {
|
||||
return useColumnsWithNumber(songs);
|
||||
}
|
||||
|
@ -1,15 +1,21 @@
|
||||
import { Button } from "antd";
|
||||
import { ISong } from "../../../models/IModels";
|
||||
import { useState } from "react";
|
||||
import { PauseCircleFilled, PlayCircleFilled } from "@ant-design/icons";
|
||||
import { height } from "@mui/system";
|
||||
import { useCurrentSongContext } from "../../../contexts/SongContexts/SongContextProvider";
|
||||
import { usePlayingContext } from "../../../contexts/SongContexts/PlayingProvider";
|
||||
|
||||
const useColumns = (songs: ISong[]) => {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const useColumns = (songs: ISong[])=> {
|
||||
|
||||
const handlePlayClick = () => {
|
||||
const {isPlaying, setIsPlaying} = usePlayingContext();
|
||||
const { songId, setSongId } = useCurrentSongContext();
|
||||
|
||||
const handlePlayClick = (songId: string) => {
|
||||
setIsPlaying(!isPlaying);
|
||||
setSongId(songId);
|
||||
};
|
||||
|
||||
|
||||
return [
|
||||
{
|
||||
title: 'Play',
|
||||
@ -18,17 +24,19 @@ const useColumns = (songs: ISong[]) => {
|
||||
width: 100,
|
||||
|
||||
render: (text: string, song: ISong) => (
|
||||
// Тут в дата-контент вставляется JSON коды иконок
|
||||
<Button data-content={isPlaying ? "\u23EF\uFE0F" : "▷"} className='play-song-button rounded' style={{background: 'transparent', display: 'contents'}} onClick={handlePlayClick}>
|
||||
<Button
|
||||
data-content={isPlaying && song.id === songId ? '┃┃' : "▷"}
|
||||
className='play-song-button rounded'
|
||||
style={{background: 'transparent', display: 'contents'}}
|
||||
onClick={() => handlePlayClick(song.id)}
|
||||
>
|
||||
|
||||
<img
|
||||
className="rounded "
|
||||
className="rounded"
|
||||
src={song.cover}
|
||||
alt={song.song_name}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
|
||||
),
|
||||
},
|
||||
{
|
||||
@ -56,4 +64,3 @@ const useColumns = (songs: ISong[]) => {
|
||||
export function GetColumns(songs: ISong[]) {
|
||||
return useColumns(songs);
|
||||
}
|
||||
|
||||
|
@ -1,73 +1,218 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Col, Grid, Row, Table } from 'antd';
|
||||
import { BackwardFilled, FastForwardFilled, FastBackwardFilled, PlayCircleFilled, PlayCircleOutlined, StepBackwardFilled, StepForwardFilled, HeartOutlined, ShareAltOutlined, PauseCircleFilled, PauseCircleOutlined } from '@ant-design/icons';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Button, Col, Grid, Menu, Row, Slider, Space, Table } from 'antd';
|
||||
import { FastForwardFilled, FastBackwardFilled, PlayCircleOutlined, HeartOutlined, ShareAltOutlined, PauseCircleOutlined, SoundOutlined, MutedOutlined, SoundFilled } from '@ant-design/icons';
|
||||
import { ISong, SongProps } from '../../models/IModels';
|
||||
import styled from 'styled-components'
|
||||
import useSound from 'use-sound';
|
||||
import { useCurrentSongContext } from '../../contexts/SongContexts/SongContextProvider';
|
||||
import { usePlayingContext } from '../../contexts/SongContexts/PlayingProvider';
|
||||
import Dropdown from 'antd/es/dropdown/dropdown';
|
||||
import Text from 'antd/es/typography/Text';
|
||||
import { useVolumeContext } from '../../contexts/VolumeContexts/VolumeProvider';
|
||||
|
||||
const Song = styled.div`
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 75%;`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 0px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 0 10px;
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
type CurrentSongProps = {
|
||||
song: ISong
|
||||
songs: ISong[]
|
||||
}
|
||||
|
||||
export function CurrentTrack({ song }: CurrentSongProps) {
|
||||
const SongName = styled.span`
|
||||
display: inline-block;
|
||||
max-width: 200px; /* Максимальная ширина */
|
||||
white-space: nowrap; /* Предотвращает перенос слов на новую строку */
|
||||
overflow: hidden; /* Скрывает текст, выходящий за пределы контейнера */
|
||||
text-overflow: ellipsis; /* Добавляет точку-эллипсис */
|
||||
padding-right: 20px; /* Добавляем небольшой отступ справа для точки */
|
||||
`;
|
||||
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
export function CurrentTrack({ song , songs}: CurrentSongProps) {
|
||||
|
||||
const soundUrl = song?.source || ''; // Пустая строка в случае отсутствия source
|
||||
const audioPlayer = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
const soundUrl = song?.source;
|
||||
|
||||
const Playlist = songs;
|
||||
|
||||
const [play, { pause, duration, sound }] = useSound(soundUrl, { volume: 0.5 });
|
||||
const {isPlaying, setIsPlaying} = usePlayingContext();
|
||||
|
||||
const { songId, setSongId } = useCurrentSongContext();
|
||||
|
||||
const playingButton = () => {
|
||||
const { volumeValue: volume, setVolumeValue: setVolume } = useVolumeContext();
|
||||
|
||||
const [ elapsed, setElapsed] = useState(0);
|
||||
|
||||
const [ duration, setDuration ] = useState(0);
|
||||
|
||||
const [ muted, setMuted ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlaying) {
|
||||
pause();
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
play();
|
||||
setIsPlaying(true);
|
||||
audioPlayer.current?.play();
|
||||
}
|
||||
else {
|
||||
audioPlayer.current?.pause();
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
const togglePlay = () => {
|
||||
if (audioPlayer.current && audioPlayer.current.src) {
|
||||
setIsPlaying(!isPlaying);
|
||||
}
|
||||
};
|
||||
|
||||
const prevTrack = () => {
|
||||
if (parseInt(songId) > 0) {
|
||||
const newId = (parseInt(songId) - 1).toString();
|
||||
setSongId(newId);
|
||||
togglePlay();
|
||||
}
|
||||
};
|
||||
|
||||
const nextTrack = () => {
|
||||
if (parseInt(songId) < songs.length - 1) {
|
||||
const newId = (parseInt(songId) + 1).toString();
|
||||
setSongId(newId);
|
||||
togglePlay();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (song) {
|
||||
play();
|
||||
audioPlayer.current?.play();
|
||||
setIsPlaying(true);
|
||||
}
|
||||
}, [song]);
|
||||
|
||||
useEffect(() => {
|
||||
if (audioPlayer.current) {
|
||||
audioPlayer.current.muted = muted;
|
||||
}
|
||||
}, [muted]);
|
||||
|
||||
useEffect(() => {
|
||||
if (audioPlayer.current) {
|
||||
audioPlayer.current.volume = volume / 100;
|
||||
|
||||
if (isPlaying) {
|
||||
setInterval(() => {
|
||||
const duration = Math.floor(audioPlayer?.current?.duration ?? 0);
|
||||
const currentTime = Math.floor(audioPlayer?.current?.currentTime ?? 0);
|
||||
|
||||
setDuration(duration - currentTime);
|
||||
setElapsed(currentTime);
|
||||
|
||||
if (currentTime === duration) {
|
||||
nextTrack();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}, [volume, isPlaying]);
|
||||
|
||||
const formatTime = (time: number) => {
|
||||
if (time) {
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.floor(time % 60);
|
||||
return `${minutes}:${seconds < 10 ? `0${seconds}` : seconds}`;
|
||||
}
|
||||
return '0:00';
|
||||
};
|
||||
|
||||
return (
|
||||
<Song>
|
||||
<div className="current-track">
|
||||
<Row className="w-full h-full bg-white opacity-90 rounded" style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Col span={4} className="flex flex-direction-row justify-center">
|
||||
<Button type="link" icon={<FastBackwardFilled className="player-button" />}></Button>
|
||||
<Button
|
||||
type="link"
|
||||
icon={isPlaying ? <PauseCircleOutlined className="player-button" onClick={playingButton} /> : <PlayCircleOutlined className="player-button" onClick={playingButton} />}
|
||||
></Button>
|
||||
<Button type="link" icon={<FastForwardFilled className="player-button" style={{ fontSize: 30 }} />}></Button>
|
||||
</Col>
|
||||
<Col style={{ width: 80 }}>
|
||||
<img src={song.cover} alt={song.cover} className="current-song-cover" />
|
||||
</Col>
|
||||
<Col span={16}>
|
||||
<Row style={{ paddingLeft: 10 }}>{song.song_name}</Row>
|
||||
<Row style={{ paddingLeft: 10 }}>{song.band_name}</Row>
|
||||
</Col>
|
||||
<Col>
|
||||
<Button type="link" icon={<HeartOutlined className="current-track-button" />}></Button>
|
||||
<Button type="link" icon={<ShareAltOutlined className="current-track-button" />}></Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<audio ref={audioPlayer} src={soundUrl} />
|
||||
<div key={song.id} className="current-track">
|
||||
<span className="w-full h-full bg-white opacity-90 rounded" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<span className='inline-flex' style={{ justifyContent: 'flex-center', alignSelf: 'flex-center', width: '27%' }}>
|
||||
<span className="flex flex-direction-row" style={{ alignItems: 'center', paddingLeft: 10, paddingRight: 10 }}>
|
||||
<Button type="link" icon={<FastBackwardFilled className="player-button" />} onClick={prevTrack}></Button>
|
||||
|
||||
<Button
|
||||
type="link"
|
||||
icon={isPlaying ?
|
||||
<PauseCircleOutlined
|
||||
className="player-button"
|
||||
onClick={togglePlay} /> : <PlayCircleOutlined className="player-button" onClick={togglePlay} />}
|
||||
/>
|
||||
|
||||
<Button type="link" icon={<FastForwardFilled className="player-button" style={{ fontSize: 30 }} />} onClick={nextTrack}></Button>
|
||||
</span>
|
||||
|
||||
<span style={{ width: 80, display: 'flex', alignSelf: 'flex-end' }}>
|
||||
<img src={song.cover} alt={song.cover} className="current-song-cover" />
|
||||
</span>
|
||||
|
||||
<span className="flex flex-direction-column justify-center items-start" style={{ paddingRight: 5, flexDirection: 'column'}} >
|
||||
<SongName style={{ paddingLeft: 10}}>{song.song_name}</SongName>
|
||||
<SongName style={{ paddingLeft: 10 }}>{song.band_name}</SongName>
|
||||
</span>
|
||||
|
||||
</span>
|
||||
|
||||
<span className='inline-flex' style={{ alignItems: 'center', width: '100%', justifyContent: 'stretch', flexDirection: 'row' }}>
|
||||
<Text style={{ width: '5%' }}>{formatTime(elapsed)}</Text>
|
||||
<Slider
|
||||
max={audioPlayer.current?.duration ?? 0}
|
||||
step={1}
|
||||
defaultValue={0}
|
||||
value={elapsed}
|
||||
styles={ { track: { backgroundColor: '#ad0000' }, rail: { backgroundColor: '#e9e9e9' }, handle: { color: '#ad0000',} }}
|
||||
style={{ width: '100%', justifyContent: 'flex-center', alignSelf: 'flex-center' }}
|
||||
tooltip={{ open: false }}
|
||||
onChange={(value) => {
|
||||
if (audioPlayer.current) {
|
||||
audioPlayer.current.currentTime = value;
|
||||
if (value >= audioPlayer.current.duration) {
|
||||
nextTrack();
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
/>
|
||||
<Text style={{ width: '5%' }}>{formatTime(audioPlayer.current?.duration ?? 0)}</Text>
|
||||
</span>
|
||||
|
||||
<span className='inline-flex' style={{ width: '14%', paddingRight: 10, justifyContent: 'flex-end', alignItems: 'center' }}>
|
||||
<Dropdown overlay={<Menu>
|
||||
<Menu.Item key="Slider">
|
||||
<Slider
|
||||
styles={ { track: { backgroundColor: '#ad0000' }, rail: { backgroundColor: '#e9e9e9' }, handle: { color: '#ad0000',} }}
|
||||
defaultValue={30} value={volume} style={{ width: 150, color: 'red' }} onChange={(value) => setVolume(value)} />
|
||||
</Menu.Item>
|
||||
</Menu>}
|
||||
trigger={['hover']}
|
||||
placement='top'>
|
||||
<Button type="link" style={{ width: 30}}
|
||||
icon={ muted || volume === 0 ?
|
||||
<SoundFilled
|
||||
style={{ color: '#ad0000'}} className="current-track-button" /> : volume <= 25 ?
|
||||
<SoundFilled className="current-track-button" /> : <SoundOutlined className="current-track-button" />}
|
||||
onClick={() => setMuted(!muted)}
|
||||
/>
|
||||
</Dropdown>
|
||||
<Button type="link" icon={<HeartOutlined className="current-track-button" />}></Button>
|
||||
<Button type="link" icon={<ShareAltOutlined className="current-track-button" />}></Button>
|
||||
</span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</Song>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
1
src/contexts/AppContexts/AppContextProvider.tsx
Normal file
1
src/contexts/AppContexts/AppContextProvider.tsx
Normal file
@ -0,0 +1 @@
|
||||
export {}
|
32
src/contexts/SongContexts/PlayingProvider.tsx
Normal file
32
src/contexts/SongContexts/PlayingProvider.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { createContext, useContext, ReactNode, useState } from "react";
|
||||
|
||||
interface PlayingProviderProps {
|
||||
children: ReactNode;
|
||||
isPlaying: boolean;
|
||||
}
|
||||
|
||||
interface PlayingContextValue {
|
||||
isPlaying: boolean;
|
||||
setIsPlaying: (isPlaying: boolean) => void;
|
||||
}
|
||||
|
||||
const PlayingContext = createContext<PlayingContextValue | undefined>(undefined);
|
||||
|
||||
export function PlayingProvider({ children, isPlaying }: PlayingProviderProps) {
|
||||
const [isPlayingSong, setIsPlaying] = useState(isPlaying);
|
||||
console.log(isPlayingSong);
|
||||
|
||||
return (
|
||||
<PlayingContext.Provider value={{ isPlaying: isPlayingSong, setIsPlaying }}>
|
||||
{children}
|
||||
</PlayingContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const usePlayingContext = function(): PlayingContextValue {
|
||||
const context = useContext(PlayingContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("usePlayingContext must be used within a PlayingProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
31
src/contexts/SongContexts/SongContextProvider.tsx
Normal file
31
src/contexts/SongContexts/SongContextProvider.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { createContext, useContext, ReactNode, useState } from "react";
|
||||
|
||||
interface CurrentSongProviderProps {
|
||||
children: ReactNode;
|
||||
songId: string;
|
||||
}
|
||||
|
||||
interface SongContextValue {
|
||||
songId: string;
|
||||
setSongId: (songId: string) => void;
|
||||
}
|
||||
|
||||
|
||||
const CurrentSongContext = createContext<SongContextValue | undefined>(undefined);
|
||||
|
||||
export const CurrentSongProvider: React.FC<CurrentSongProviderProps> = ({ children, songId }) => {
|
||||
const [currentSongId, setCurrentSongId] = useState(songId);
|
||||
|
||||
return (
|
||||
<CurrentSongContext.Provider value={{ songId: currentSongId, setSongId: setCurrentSongId }}>
|
||||
{children}
|
||||
</CurrentSongContext.Provider>
|
||||
);
|
||||
};
|
||||
export const useCurrentSongContext = function(): SongContextValue {
|
||||
const context = useContext(CurrentSongContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useCurrentSongContext must be used within a CurrentSongProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
31
src/contexts/VolumeContexts/VolumeProvider.tsx
Normal file
31
src/contexts/VolumeContexts/VolumeProvider.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { createContext, useContext, ReactNode, useState } from "react";
|
||||
|
||||
interface VolumeProviderProps {
|
||||
children: ReactNode;
|
||||
volumeValue: number;
|
||||
}
|
||||
|
||||
interface SongContextValue {
|
||||
volumeValue: number;
|
||||
setVolumeValue: (value: number) => void;
|
||||
}
|
||||
|
||||
|
||||
const VolumeContext = createContext<SongContextValue | undefined>(undefined);
|
||||
|
||||
export const VolumeProvider: React.FC<VolumeProviderProps> = ({ children, volumeValue: value }) => {
|
||||
const [volumeId, setVolumeId] = useState(value);
|
||||
|
||||
return (
|
||||
<VolumeContext.Provider value={{ volumeValue: volumeId, setVolumeValue: setVolumeId }}>
|
||||
{children}
|
||||
</VolumeContext.Provider>
|
||||
);
|
||||
};
|
||||
export const useVolumeContext = function(): SongContextValue {
|
||||
const context = useContext(VolumeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useVolumeContext must be used within a VolumeProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
@ -1,13 +1,13 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getSongs, getGenres, getPlaylists, getAds } from "../API/api";
|
||||
import { getSongs, getGenres, getPlaylists, getAds, getSong } from "../API/api";
|
||||
import { AdBlock } from "../components/layoutComponents/AdBlock";
|
||||
import { MenuBlock } from "../components/layoutComponents/MenuBlock";
|
||||
import { CurrentTrack } from "../components/songComponents/CurrentTrack";
|
||||
import { IPlaylist, ISong, IAlbum, IGenre, IAdvertisement, IBand } from "../models/IModels";
|
||||
import { useCurrentSongContext } from "../contexts/SongContexts/SongContextProvider";
|
||||
|
||||
|
||||
export function Homepage({song_id = "0"}) {
|
||||
const [currentSong, setCurrentSong] = useState<ISong | null>(null);
|
||||
export function Homepage() {
|
||||
|
||||
const [albums, setAlbums] = useState<IAlbum[]>([]);
|
||||
const [songs, setSongs] = useState<ISong[]>([]);
|
||||
@ -17,6 +17,8 @@ export function Homepage({song_id = "0"}) {
|
||||
const [playlists, setPlaylist] = React.useState<IPlaylist[]>([]);
|
||||
const [genres, setGenres] = React.useState<IGenre[]>([]);
|
||||
|
||||
const currentSongId = useCurrentSongContext();
|
||||
|
||||
const fetchData = async () => {
|
||||
const [responseSongs, responseAds, responsePlaylists, responseGenres] = await Promise.all([
|
||||
getSongs(),
|
||||
@ -35,10 +37,15 @@ export function Homepage({song_id = "0"}) {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const currentSong = songs.find(song => song.id === currentSongId.songId);
|
||||
console.log('Айди песни:');
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuBlock playlists={[]} songs={songs} albums={albums} genres={[]}/><AdBlock ads={ads}/>
|
||||
{currentSong && <CurrentTrack song={currentSong} />}
|
||||
{currentSong && <CurrentTrack song={currentSong} songs={songs}/>}
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
@ -3,7 +3,6 @@ import { Form, Input, Button, Checkbox } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import ReCAPTCHA from "react-google-recaptcha";
|
||||
import React, { useState } from "react";
|
||||
import FormItem from "antd/es/form/FormItem";
|
||||
|
||||
export function Registerpage() {
|
||||
const [verified, setVerified] = useState(false);
|
||||
|
Loading…
Reference in New Issue
Block a user