Update files
This commit is contained in:
parent
046066a079
commit
56538d098f
|
|
@ -0,0 +1,126 @@
|
||||||
|
# Скрипт удаления всех альбомов и треков
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
|
||||||
|
Скрипт `deleteAllMusic.js` безвозвратно удаляет все музыкальные данные из базы данных:
|
||||||
|
- Все треки (Tracks)
|
||||||
|
- Все альбомы (Albums)
|
||||||
|
- Все избранные треки (FavoriteTrack)
|
||||||
|
- Все ссылки на треки в постах (attachedTrack устанавливается в null)
|
||||||
|
|
||||||
|
**⚠️ ВНИМАНИЕ: Это действие необратимо!**
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Для Windows (CMD/PowerShell):
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
cd backend
|
||||||
|
node scripts\deleteAllMusic.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Для Linux/Mac:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
node scripts/deleteAllMusic.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Или из корня проекта:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
node backend/scripts/deleteAllMusic.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что делает скрипт
|
||||||
|
|
||||||
|
1. **Подключается к MongoDB** используя настройки из `.env`
|
||||||
|
2. **Показывает статистику** текущего количества записей
|
||||||
|
3. **Удаляет данные в следующем порядке:**
|
||||||
|
- Избранные треки (FavoriteTrack)
|
||||||
|
- Обнуляет attachedTrack в постах
|
||||||
|
- Удаляет все треки (Track)
|
||||||
|
- Удаляет все альбомы (Album)
|
||||||
|
4. **Показывает финальную статистику**
|
||||||
|
|
||||||
|
## Пример вывода
|
||||||
|
|
||||||
|
```
|
||||||
|
🔌 Подключение к MongoDB...
|
||||||
|
✅ Подключено к MongoDB
|
||||||
|
|
||||||
|
📊 Текущая статистика:
|
||||||
|
- Треков: 150
|
||||||
|
- Альбомов: 25
|
||||||
|
- Избранных треков: 500
|
||||||
|
- Постов с прикрепленными треками: 80
|
||||||
|
|
||||||
|
🗑️ Начало удаления...
|
||||||
|
|
||||||
|
1️⃣ Удаление избранных треков...
|
||||||
|
✅ Удалено избранных треков: 500
|
||||||
|
|
||||||
|
2️⃣ Обнуление прикрепленных треков в постах...
|
||||||
|
✅ Обновлено постов: 80
|
||||||
|
|
||||||
|
3️⃣ Удаление треков...
|
||||||
|
✅ Удалено треков: 150
|
||||||
|
|
||||||
|
4️⃣ Удаление альбомов...
|
||||||
|
✅ Удалено альбомов: 25
|
||||||
|
|
||||||
|
📊 Финальная статистика:
|
||||||
|
- Треков: 0
|
||||||
|
- Альбомы: 0
|
||||||
|
- Избранных треков: 0
|
||||||
|
- Постов с прикрепленными треками: 0
|
||||||
|
|
||||||
|
✅ Все альбомы и треки успешно удалены!
|
||||||
|
|
||||||
|
🔌 Отключено от MongoDB
|
||||||
|
```
|
||||||
|
|
||||||
|
## Важные замечания
|
||||||
|
|
||||||
|
- ⚠️ **Физические файлы не удаляются** - скрипт удаляет только записи из базы данных. Файлы в `backend/uploads/music/` остаются на диске. Для полной очистки нужно вручную удалить файлы.
|
||||||
|
|
||||||
|
- ⚠️ **Исполнители (Artists) не удаляются** - они остаются в базе, так как могут быть переиспользованы при загрузке новых треков.
|
||||||
|
|
||||||
|
- ✅ **Посты не удаляются** - только обнуляется поле `attachedTrack`.
|
||||||
|
|
||||||
|
## Дополнительная очистка файлов
|
||||||
|
|
||||||
|
Если нужно также удалить физические файлы (аудио и обложки):
|
||||||
|
|
||||||
|
### Windows (PowerShell):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Удалить все файлы в папке музыки (сохранив структуру папок)
|
||||||
|
Remove-Item -Path "backend\uploads\music\*" -Include *.mp3,*.wav,*.ogg,*.m4a,*.flac,*.jpg,*.jpeg,*.png -Force
|
||||||
|
|
||||||
|
# Или полностью удалить папку и создать заново
|
||||||
|
Remove-Item -Path "backend\uploads\music" -Recurse -Force
|
||||||
|
New-Item -Path "backend\uploads\music" -ItemType Directory
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux/Mac:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Удалить все файлы в папке музыки
|
||||||
|
rm -f backend/uploads/music/*.{mp3,wav,ogg,m4a,flac,jpg,jpeg,png}
|
||||||
|
|
||||||
|
# Или полностью удалить папку и создать заново
|
||||||
|
rm -rf backend/uploads/music
|
||||||
|
mkdir -p backend/uploads/music
|
||||||
|
```
|
||||||
|
|
||||||
|
## Восстановление после удаления
|
||||||
|
|
||||||
|
После выполнения скрипта невозможно восстановить удаленные данные из базы данных. Если у вас есть резервная копия (backup), вы можете восстановить данные из неё.
|
||||||
|
|
||||||
|
Для создания резервной копии перед удалением используйте:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
node backend/scripts/backup.js
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Исправление ошибки 413 при загрузке альбомов
|
||||||
|
|
||||||
|
## Проблема
|
||||||
|
При загрузке ZIP альбомов возникает ошибка 413 "Request Entity Too Large", даже после увеличения лимитов в Express и multer.
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Ошибка 413 возникает на уровне Nginx, который проксирует запросы к backend. Нужно увеличить `client_max_body_size` в конфигурации Nginx.
|
||||||
|
|
||||||
|
### Вариант 1: Если используется Nginx для проксирования к backend
|
||||||
|
|
||||||
|
Добавьте или увеличьте `client_max_body_size` в конфигурации Nginx:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# Увеличить лимит загрузки до 110MB (больше чем на backend для безопасности)
|
||||||
|
client_max_body_size 110M;
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Увеличить таймауты для больших загрузок
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Вариант 2: Если используется docker-compose с nginx
|
||||||
|
|
||||||
|
Обновите `nginx-moderation-production.conf` или создайте отдельный конфиг для основного приложения:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# Увеличить лимит загрузки
|
||||||
|
client_max_body_size 110M;
|
||||||
|
|
||||||
|
# Увеличить таймауты
|
||||||
|
client_body_timeout 300s;
|
||||||
|
client_header_timeout 300s;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Вариант 3: Для production на сервере
|
||||||
|
|
||||||
|
Если приложение развернуто на сервере, нужно обновить конфигурацию Nginx:
|
||||||
|
|
||||||
|
1. Отредактируйте конфигурацию:
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/nginx/sites-available/nakama
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Добавьте или обновите:
|
||||||
|
```nginx
|
||||||
|
client_max_body_size 110M;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Проверьте конфигурацию:
|
||||||
|
```bash
|
||||||
|
sudo nginx -t
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Перезапустите Nginx:
|
||||||
|
```bash
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
После применения изменений попробуйте загрузить ZIP альбом размером до 105MB. Ошибка 413 должна исчезнуть.
|
||||||
|
|
||||||
|
## Примечание
|
||||||
|
|
||||||
|
Лимиты должны быть установлены в следующем порядке (от большего к меньшему):
|
||||||
|
1. Nginx: `client_max_body_size 110M` (больше всех)
|
||||||
|
2. Express: `limit: '105mb'`
|
||||||
|
3. Multer: `fileSize: 105 * 1024 * 1024`
|
||||||
|
|
||||||
|
Это обеспечивает, что запрос доходит до backend без ошибок.
|
||||||
|
|
||||||
|
|
@ -215,6 +215,12 @@ router.post('/', authenticate, strictPostLimiter, postCreationLimiter, fileUploa
|
||||||
|
|
||||||
await post.save();
|
await post.save();
|
||||||
await post.populate('author', 'username firstName lastName photoUrl');
|
await post.populate('author', 'username firstName lastName photoUrl');
|
||||||
|
if (post.attachedTrack) {
|
||||||
|
await post.populate({
|
||||||
|
path: 'attachedTrack',
|
||||||
|
populate: { path: 'artist album' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Увеличить счетчики использования тегов
|
// Увеличить счетчики использования тегов
|
||||||
if (parsedTags.length > 0) {
|
if (parsedTags.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
/**
|
||||||
|
* Скрипт для удаления всех альбомов и треков из базы данных
|
||||||
|
*
|
||||||
|
* ВНИМАНИЕ: Этот скрипт безвозвратно удалит:
|
||||||
|
* - Все треки (Tracks)
|
||||||
|
* - Все альбомы (Albums)
|
||||||
|
* - Все избранные треки (FavoriteTrack)
|
||||||
|
* - Все ссылки на треки в постах (attachedTrack будет установлен в null)
|
||||||
|
*
|
||||||
|
* Использование:
|
||||||
|
* node backend/scripts/deleteAllMusic.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('dotenv').config({ path: require('path').join(__dirname, '../.env') });
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const Track = require('../models/Track');
|
||||||
|
const Album = require('../models/Album');
|
||||||
|
const FavoriteTrack = require('../models/FavoriteTrack');
|
||||||
|
const Post = require('../models/Post');
|
||||||
|
|
||||||
|
const config = require('../config/index');
|
||||||
|
|
||||||
|
async function deleteAllMusic() {
|
||||||
|
try {
|
||||||
|
console.log('🔌 Подключение к MongoDB...');
|
||||||
|
await mongoose.connect(config.mongoUri);
|
||||||
|
console.log('✅ Подключено к MongoDB\n');
|
||||||
|
|
||||||
|
// Подсчет перед удалением
|
||||||
|
const tracksCount = await Track.countDocuments();
|
||||||
|
const albumsCount = await Album.countDocuments();
|
||||||
|
const favoritesCount = await FavoriteTrack.countDocuments();
|
||||||
|
const postsWithTracksCount = await Post.countDocuments({ attachedTrack: { $ne: null } });
|
||||||
|
|
||||||
|
console.log('📊 Текущая статистика:');
|
||||||
|
console.log(` - Треков: ${tracksCount}`);
|
||||||
|
console.log(` - Альбомов: ${albumsCount}`);
|
||||||
|
console.log(` - Избранных треков: ${favoritesCount}`);
|
||||||
|
console.log(` - Постов с прикрепленными треками: ${postsWithTracksCount}\n`);
|
||||||
|
|
||||||
|
if (tracksCount === 0 && albumsCount === 0) {
|
||||||
|
console.log('✅ В базе данных нет треков и альбомов для удаления');
|
||||||
|
await mongoose.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🗑️ Начало удаления...\n');
|
||||||
|
|
||||||
|
// 1. Удалить все избранные треки
|
||||||
|
console.log('1️⃣ Удаление избранных треков...');
|
||||||
|
const favoritesResult = await FavoriteTrack.deleteMany({});
|
||||||
|
console.log(` ✅ Удалено избранных треков: ${favoritesResult.deletedCount}`);
|
||||||
|
|
||||||
|
// 2. Обнулить attachedTrack во всех постах
|
||||||
|
console.log('\n2️⃣ Обнуление прикрепленных треков в постах...');
|
||||||
|
const postsResult = await Post.updateMany(
|
||||||
|
{ attachedTrack: { $ne: null } },
|
||||||
|
{ $set: { attachedTrack: null } }
|
||||||
|
);
|
||||||
|
console.log(` ✅ Обновлено постов: ${postsResult.modifiedCount}`);
|
||||||
|
|
||||||
|
// 3. Удалить все треки
|
||||||
|
console.log('\n3️⃣ Удаление треков...');
|
||||||
|
const tracksResult = await Track.deleteMany({});
|
||||||
|
console.log(` ✅ Удалено треков: ${tracksResult.deletedCount}`);
|
||||||
|
|
||||||
|
// 4. Удалить все альбомы
|
||||||
|
console.log('\n4️⃣ Удаление альбомов...');
|
||||||
|
const albumsResult = await Album.deleteMany({});
|
||||||
|
console.log(` ✅ Удалено альбомов: ${albumsResult.deletedCount}`);
|
||||||
|
|
||||||
|
// Финальная статистика
|
||||||
|
console.log('\n📊 Финальная статистика:');
|
||||||
|
const finalTracksCount = await Track.countDocuments();
|
||||||
|
const finalAlbumsCount = await Album.countDocuments();
|
||||||
|
const finalFavoritesCount = await FavoriteTrack.countDocuments();
|
||||||
|
const finalPostsWithTracksCount = await Post.countDocuments({ attachedTrack: { $ne: null } });
|
||||||
|
|
||||||
|
console.log(` - Треков: ${finalTracksCount}`);
|
||||||
|
console.log(` - Альбомов: ${finalAlbumsCount}`);
|
||||||
|
console.log(` - Избранных треков: ${finalFavoritesCount}`);
|
||||||
|
console.log(` - Постов с прикрепленными треками: ${finalPostsWithTracksCount}`);
|
||||||
|
|
||||||
|
console.log('\n✅ Все альбомы и треки успешно удалены!');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка при удалении:', error);
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
await mongoose.disconnect();
|
||||||
|
console.log('\n🔌 Отключено от MongoDB');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск скрипта
|
||||||
|
deleteAllMusic();
|
||||||
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
import { X, Play, Pause, SkipBack, SkipForward, Heart, Download, Music, Volume2, VolumeX } from 'lucide-react'
|
import { X, Play, Pause, SkipBack, SkipForward, Heart, Download, Music, Volume2, VolumeX } from 'lucide-react'
|
||||||
import { useMusicPlayer } from '../contexts/MusicPlayerContext'
|
import { useMusicPlayer } from '../contexts/MusicPlayerContext'
|
||||||
import { hapticFeedback, getTelegramUser } from '../utils/telegram'
|
import { hapticFeedback, getTelegramUser } from '../utils/telegram'
|
||||||
|
|
@ -7,6 +8,7 @@ import api from '../utils/api'
|
||||||
import './FullPlayer.css'
|
import './FullPlayer.css'
|
||||||
|
|
||||||
export default function FullPlayer() {
|
export default function FullPlayer() {
|
||||||
|
const location = useLocation()
|
||||||
const {
|
const {
|
||||||
currentTrack,
|
currentTrack,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
|
|
@ -23,11 +25,12 @@ export default function FullPlayer() {
|
||||||
changeVolume,
|
changeVolume,
|
||||||
setIsExpanded
|
setIsExpanded
|
||||||
} = useMusicPlayer()
|
} = useMusicPlayer()
|
||||||
|
|
||||||
const [isFavorite, setIsFavorite] = useState(false)
|
const [isFavorite, setIsFavorite] = useState(false)
|
||||||
const [showVolume, setShowVolume] = useState(false)
|
const [showVolume, setShowVolume] = useState(false)
|
||||||
|
|
||||||
if (!isExpanded || !currentTrack) return null
|
// Скрыть плеер на странице profile
|
||||||
|
if (location.pathname === '/profile' || !isExpanded || !currentTrack) return null
|
||||||
|
|
||||||
const formatTime = (seconds) => {
|
const formatTime = (seconds) => {
|
||||||
if (!seconds || isNaN(seconds)) return '0:00'
|
if (!seconds || isNaN(seconds)) return '0:00'
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.ladder-button {
|
.ladder-button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 100px;
|
bottom: 140px;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ export default function LadderButton() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
// Скрыть кнопку на странице ladder
|
// Скрыть кнопку на странице ladder и profile
|
||||||
if (location.pathname === '/ladder') {
|
if (location.pathname === '/ladder' || location.pathname === '/profile') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
import { Play, Pause, SkipForward, Music } from 'lucide-react'
|
import { Play, Pause, SkipForward, Music } from 'lucide-react'
|
||||||
import { useMusicPlayer } from '../contexts/MusicPlayerContext'
|
import { useMusicPlayer } from '../contexts/MusicPlayerContext'
|
||||||
import { hapticFeedback } from '../utils/telegram'
|
import { hapticFeedback } from '../utils/telegram'
|
||||||
import './MiniPlayer.css'
|
import './MiniPlayer.css'
|
||||||
|
|
||||||
export default function MiniPlayer() {
|
export default function MiniPlayer() {
|
||||||
|
const location = useLocation()
|
||||||
const {
|
const {
|
||||||
currentTrack,
|
currentTrack,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
|
|
@ -14,7 +16,8 @@ export default function MiniPlayer() {
|
||||||
toggleExpanded
|
toggleExpanded
|
||||||
} = useMusicPlayer()
|
} = useMusicPlayer()
|
||||||
|
|
||||||
if (!currentTrack) return null
|
// Скрыть плеер на странице profile
|
||||||
|
if (location.pathname === '/profile' || !currentTrack) return null
|
||||||
|
|
||||||
const progressPercent = duration > 0 ? (progress / duration) * 100 : 0
|
const progressPercent = duration > 0 ? (progress / duration) * 100 : 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
.music-attachment {
|
.music-attachment {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border: 1px solid var(--divider-color);
|
border: 1px solid var(--divider-color);
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
padding: 12px;
|
padding: 8px 12px;
|
||||||
|
margin-top: 8px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,9 +15,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-attachment-cover {
|
.music-attachment-cover {
|
||||||
width: 48px;
|
width: 32px;
|
||||||
height: 48px;
|
height: 32px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -36,12 +37,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-attachment-info {
|
.music-attachment-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-attachment-title {
|
.music-attachment-title {
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
@ -50,13 +54,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-attachment-artist {
|
.music-attachment-artist {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.music-attachment-title::after {
|
||||||
|
content: '';
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0 4px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-attachment-title:has(+ .music-attachment-artist)::after {
|
||||||
|
content: ' • ';
|
||||||
|
}
|
||||||
|
|
||||||
.music-attachment-play,
|
.music-attachment-play,
|
||||||
.music-attachment-remove {
|
.music-attachment-remove {
|
||||||
background: none;
|
background: none;
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,13 @@ export default function MusicAttachment({ track, onRemove, showRemove = false })
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="music-attachment-info">
|
<div className="music-attachment-info">
|
||||||
<div className="music-attachment-title">{track.title}</div>
|
<div className="music-attachment-title">{track.title || 'Unknown Track'}</div>
|
||||||
<div className="music-attachment-artist">{track.artist?.name || 'Unknown'}</div>
|
{track.artist?.name && (
|
||||||
|
<>
|
||||||
|
<span style={{ color: 'var(--text-secondary)', margin: '0 4px' }}>•</span>
|
||||||
|
<div className="music-attachment-artist">{track.artist.name}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className="music-attachment-play" onClick={handlePlay}>
|
<button className="music-attachment-play" onClick={handlePlay}>
|
||||||
|
|
|
||||||
|
|
@ -195,13 +195,6 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Прикрепленная музыка */}
|
|
||||||
{post.attachedTrack && (
|
|
||||||
<div className="post-music">
|
|
||||||
<MusicAttachment track={post.attachedTrack} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Изображения */}
|
{/* Изображения */}
|
||||||
{images.length > 0 && (
|
{images.length > 0 && (
|
||||||
<div className="post-images">
|
<div className="post-images">
|
||||||
|
|
@ -258,6 +251,20 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Музыкальный трек (после изображений, компактно) */}
|
||||||
|
{post.attachedTrack && (
|
||||||
|
<div className="post-music">
|
||||||
|
<MusicAttachment track={post.attachedTrack} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Прикрепленная музыка (после изображений, компактно) */}
|
||||||
|
{post.attachedTrack && (
|
||||||
|
<div className="post-music">
|
||||||
|
<MusicAttachment track={post.attachedTrack} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Теги */}
|
{/* Теги */}
|
||||||
<div className="post-tags">
|
<div className="post-tags">
|
||||||
{post.tags.map((tag, index) => {
|
{post.tags.map((tag, index) => {
|
||||||
|
|
|
||||||
|
|
@ -3,36 +3,15 @@ import { Music, User } from 'lucide-react'
|
||||||
import { hapticFeedback } from '../utils/telegram'
|
import { hapticFeedback } from '../utils/telegram'
|
||||||
import './Media.css'
|
import './Media.css'
|
||||||
|
|
||||||
// Иконка лисы (SVG в стиле аниме)
|
// Иконка лисы из Icons8
|
||||||
const FoxIcon = ({ size = 48 }) => (
|
const FoxIcon = ({ size = 48 }) => (
|
||||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<img
|
||||||
{/* Голова */}
|
src="https://img.icons8.com/windows/32/fox.png"
|
||||||
<path d="M12 8C15 8 17 10 17 12.5C17 13.5 16.5 14.5 16 15" />
|
alt="Fox"
|
||||||
<path d="M12 8C9 8 7 10 7 12.5C7 13.5 7.5 14.5 8 15" />
|
width={size}
|
||||||
|
height={size}
|
||||||
{/* Уши */}
|
style={{ objectFit: 'contain' }}
|
||||||
<path d="M10 6L9 3L8 6" />
|
/>
|
||||||
<path d="M14 6L15 3L16 6" />
|
|
||||||
|
|
||||||
{/* Мордочка */}
|
|
||||||
<ellipse cx="12" cy="13" rx="4.5" ry="3.5" />
|
|
||||||
|
|
||||||
{/* Глаза */}
|
|
||||||
<circle cx="10" cy="12.5" r="1.2" fill="currentColor" />
|
|
||||||
<circle cx="14" cy="12.5" r="1.2" fill="currentColor" />
|
|
||||||
|
|
||||||
{/* Нос */}
|
|
||||||
<path d="M12 14.5L11.5 15.5L12 16.5L12.5 15.5Z" fill="currentColor" />
|
|
||||||
|
|
||||||
{/* Рот */}
|
|
||||||
<path d="M10.5 15C11 16 11.5 16.5 12 16.5C12.5 16.5 13 16 13.5 15" />
|
|
||||||
|
|
||||||
{/* Туловище */}
|
|
||||||
<ellipse cx="12" cy="18.5" rx="3.5" ry="2.5" />
|
|
||||||
|
|
||||||
{/* Хвост */}
|
|
||||||
<path d="M15.5 17C17.5 16.5 19 18 19.5 20C20 22 19 23.5 17.5 24" />
|
|
||||||
</svg>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export default function Media({ user }) {
|
export default function Media({ user }) {
|
||||||
|
|
@ -84,7 +63,11 @@ export default function Media({ user }) {
|
||||||
style={{ '--category-color': category.color }}
|
style={{ '--category-color': category.color }}
|
||||||
>
|
>
|
||||||
<div className="media-card-icon">
|
<div className="media-card-icon">
|
||||||
<Icon size={48} strokeWidth={2} />
|
{category.id === 'furry' ? (
|
||||||
|
<Icon size={48} />
|
||||||
|
) : (
|
||||||
|
<Icon size={48} strokeWidth={2} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h2 className="media-card-title">{category.name}</h2>
|
<h2 className="media-card-title">{category.name}</h2>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue