feat: Add snackbar notifications for various actions

This commit is contained in:
Peifan Li
2025-11-22 19:52:41 -05:00
parent ebe84e38d6
commit 68a993e074
5 changed files with 164 additions and 93 deletions

View File

@@ -6,41 +6,40 @@
![Nov-21-2025 16-53-03](https://github.com/user-attachments/assets/1f23ba18-da7a-4011-b716-6daa628552a5)
## 功能特点
- 通过简单的 URL 输入下载 YouTube 和 Bilibili 视频
- 自动保存视频缩略图
- 浏览和播放已下载的视频
- 查看特定作者的视频
- 将视频整理到收藏夹中
- 将视频添加到多个收藏夹
- 适用于所有设备的响应式设计
- **视频下载**通过简单的 URL 输入下载 YouTube 和 Bilibili 视频
- **Bilibili 支持**支持下载单个视频、多P视频以及整个合集/系列。
- **并行下载**:支持队列下载,可同时追踪多个下载任务的进度。
- **本地库**:自动保存视频缩略图和元数据,提供丰富的浏览体验。
- **搜索功能**:支持在本地库中搜索视频,或在线搜索 YouTube 视频。
- **收藏夹**:创建自定义收藏夹以整理您的视频。
- **现代化 UI**:响应式深色主题界面,包含“返回主页”功能和玻璃拟态效果。
- **主题支持**:支持在明亮和深色模式之间切换。
## 目录结构
```
mytube/
├── backend/ # Express.js 后端
├── backend/ # Express.js 后端 (TypeScript)
│ ├── src/ # 源代码
│ │ ├── config/ # 配置文件
│ │ ├── controllers/ # 路由控制器
│ │ ├── routes/ # API 路由
│ │ ├── services/ # 业务逻辑服务
│ │ ── utils/ # 工具函数
│ │ ── utils/ # 工具函数
│ │ └── server.ts # 主服务器文件
│ ├── uploads/ # 上传文件目录
│ │ ├── videos/ # 下载的视频
│ │ └── images/ # 下载的缩略图
│ └── server.js # 主服务器文件
├── frontend/ # React.js 前端
│ ├── public/ # 静态资源
│ └── package.json # 后端依赖
├── frontend/ # React.js 前端 (Vite + TypeScript)
│ ├── src/ # 源代码
│ │ ├── assets/ # 图片和样式
│ │ ├── components/ # React 组件
│ │ ── pages/ # 页面组件
│ └── index.html # HTML 入口点
├── start.sh # Unix/Mac 启动脚本
├── start.bat # Windows 启动脚本
│ │ ── pages/ # 页面组件
│ └── theme.ts # 主题配置
│ └── package.json # 前端依赖
├── build-and-push.sh # Docker 构建脚本
├── docker-compose.yml # Docker Compose 配置
├── DEPLOYMENT.md # 部署指南
@@ -59,7 +58,7 @@ mytube/
1. 克隆仓库:
```
```bash
git clone <repository-url>
cd mytube
```
@@ -68,13 +67,13 @@ mytube/
您可以使用一条命令安装根目录、前端和后端的所有依赖:
```
```bash
npm run install:all
```
或者手动安装:
```
```bash
npm install
cd frontend && npm install
cd ../backend && npm install
@@ -82,15 +81,15 @@ mytube/
#### 使用 npm 脚本
或者,您可以使用 npm 脚本:
您可以在根目录下使用 npm 脚本:
```
```bash
npm run dev # 以开发模式启动前端和后端
```
其他可用脚本:
```
```bash
npm run start # 以生产模式启动前端和后端
npm run build # 为生产环境构建前端
```
@@ -102,52 +101,55 @@ npm run build # 为生产环境构建前端
## API 端点
- `POST /api/download/youtube` - 下载 YouTube 视频
- `POST /api/download/bilibili` - 下载 Bilibili 视频
### 视频
- `POST /api/download` - 下载视频 (YouTube 或 Bilibili)
- `GET /api/videos` - 获取所有已下载的视频
- `GET /api/videos/:id` - 获取特定视频
- `DELETE /api/videos/:id` - 删除视频
- `GET /api/search` - 在线搜索视频
- `GET /api/download-status` - 获取当前下载状态
- `GET /api/check-bilibili-parts` - 检查 Bilibili 视频是否包含多个分P
- `GET /api/check-bilibili-collection` - 检查 Bilibili URL 是否为合集/系列
### 收藏夹
- `GET /api/collections` - 获取所有收藏夹
- `POST /api/collections` - 创建新收藏夹
- `PUT /api/collections/:id` - 更新收藏夹
- `PUT /api/collections/:id` - 更新收藏夹 (添加/移除视频)
- `DELETE /api/collections/:id` - 删除收藏夹
- `POST /api/collections/:id/videos` - 将视频添加到收藏夹
- `DELETE /api/collections/:id/videos/:videoId` - 从收藏夹中移除视频
## 收藏夹功能
MyTube 允许您将视频整理到收藏夹中:
- **创建收藏夹**:创建自定义收藏夹以对视频进行分类
- **添加到收藏夹**:直接从视频播放器将视频添加到一个或多个收藏夹
- **从收藏夹中移除**一键从收藏夹中移除视频
- **浏览收藏夹**:在侧边栏查看所有收藏夹,并按收藏夹浏览视频
- **创建收藏夹**:创建自定义收藏夹以对视频进行分类
- **添加到收藏夹**:直接从视频播放器或管理页面将视频添加到一个或多个收藏夹
- **从收藏夹中移除**轻松从收藏夹中移除视频
- **浏览收藏夹**:在侧边栏查看所有收藏夹,并按收藏夹浏览视频
## 用户界面
该应用具有现代化、深色主题的 UI包括
该应用具有现代化、高级感的 UI包括
- 适用于桌面和移动设备的响应式设计
- 方便浏览的视频网格布局
- 带有收藏夹管理的视频播放器
- 按作者和收藏夹筛选
- 用于查找视频的搜索功能
- **深色/明亮模式**:根据您的喜好切换主题。
- **响应式设计**:在桌面和移动设备上无缝运行。
- **视频网格**:便于浏览的视频库网格布局。
- **确认模态框**:带有自定义确认对话框的安全删除功能。
- **搜索**:集成的搜索栏,用于查找本地和在线内容。
## 环境变量
该应用使用环境变量进行配置。以下是设置方法:
该应用使用环境变量进行配置。
### 前端frontend 目录中的 .env 文件)
### 前端 (`frontend/.env`)
```
```env
VITE_API_URL=http://localhost:5551/api
VITE_BACKEND_URL=http://localhost:5551
VITE_APP_PORT=5556
```
### 后端backend 目录中的 .env 文件)
### 后端 (`backend/.env`)
```
```env
PORT=5551
UPLOAD_DIR=uploads
VIDEO_DIR=uploads/videos
@@ -155,7 +157,7 @@ IMAGE_DIR=uploads/images
MAX_FILE_SIZE=500000000
```
前端和后端目录中的 `.env.example` 文件复制以创建自己的 `.env` 文件,并用所需的值替换占位符
复制前端和后端目录中的 `.env.example` 文件以创建自己的 `.env` 文件。
## 部署

View File

@@ -8,38 +8,38 @@ A YouTube/Bilibili video downloader and player application that allows you to do
## Features
- Download YouTube and Bilibili videos with a simple URL input
- Automatically save video thumbnails
- Browse and play downloaded videos
- View videos by specific authors
- Organize videos into collections
- Add videos to multiple collections
- Responsive design that works on all devices
- **Video Downloading**: Download YouTube and Bilibili videos with a simple URL input.
- **Bilibili Support**: Support for downloading single videos, multi-part videos, and entire collections/series.
- **Parallel Downloads**: Queue multiple downloads and track their progress simultaneously.
- **Local Library**: Automatically save video thumbnails and metadata for a rich browsing experience.
- **Search**: Search for videos locally in your library or online via YouTube.
- **Collections**: Organize videos into custom collections for easy access.
- **Modern UI**: Responsive, dark-themed interface with a "Back to Home" feature and glassmorphism effects.
- **Theme Support**: Toggle between Light and Dark modes.
## Directory Structure
```
mytube/
├── backend/ # Express.js backend
├── backend/ # Express.js backend (TypeScript)
│ ├── src/ # Source code
│ │ ├── config/ # Configuration files
│ │ ├── controllers/ # Route controllers
│ │ ├── routes/ # API routes
│ │ ├── services/ # Business logic services
│ │ ── utils/ # Utility functions
│ │ ── utils/ # Utility functions
│ │ └── server.ts # Main server file
│ ├── uploads/ # Uploaded files directory
│ │ ├── videos/ # Downloaded videos
│ │ └── images/ # Downloaded thumbnails
│ └── server.js # Main server file
├── frontend/ # React.js frontend
│ ├── public/ # Static assets
│ └── package.json # Backend dependencies
├── frontend/ # React.js frontend (Vite + TypeScript)
│ ├── src/ # Source code
│ │ ├── assets/ # Images and styles
│ │ ├── components/ # React components
│ │ ── pages/ # Page components
│ └── index.html # HTML entry point
├── start.sh # Unix/Mac startup script
├── start.bat # Windows startup script
│ │ ── pages/ # Page components
│ └── theme.ts # Theme configuration
│ └── package.json # Frontend dependencies
├── build-and-push.sh # Docker build script
├── docker-compose.yml # Docker Compose configuration
├── DEPLOYMENT.md # Deployment guide
@@ -58,7 +58,7 @@ mytube/
1. Clone the repository:
```
```bash
git clone <repository-url>
cd mytube
```
@@ -67,13 +67,13 @@ mytube/
You can install all dependencies for the root, frontend, and backend with a single command:
```
```bash
npm run install:all
```
Or manually:
```
```bash
npm install
cd frontend && npm install
cd ../backend && npm install
@@ -81,15 +81,15 @@ mytube/
#### Using npm Scripts
Alternatively, you can use npm scripts:
You can use npm scripts from the root directory:
```
```bash
npm run dev # Start both frontend and backend in development mode
```
Other available scripts:
```
```bash
npm run start # Start both frontend and backend in production mode
npm run build # Build the frontend for production
```
@@ -101,52 +101,55 @@ npm run build # Build the frontend for production
## API Endpoints
- `POST /api/download/youtube` - Download a YouTube video
- `POST /api/download/bilibili` - Download a Bilibili video
### Videos
- `POST /api/download` - Download a video (YouTube or Bilibili)
- `GET /api/videos` - Get all downloaded videos
- `GET /api/videos/:id` - Get a specific video
- `DELETE /api/videos/:id` - Delete a video
- `GET /api/search` - Search for videos online
- `GET /api/download-status` - Get status of active downloads
- `GET /api/check-bilibili-parts` - Check if a Bilibili video has multiple parts
- `GET /api/check-bilibili-collection` - Check if a Bilibili URL is a collection/series
### Collections
- `GET /api/collections` - Get all collections
- `POST /api/collections` - Create a new collection
- `PUT /api/collections/:id` - Update a collection
- `PUT /api/collections/:id` - Update a collection (add/remove videos)
- `DELETE /api/collections/:id` - Delete a collection
- `POST /api/collections/:id/videos` - Add a video to a collection
- `DELETE /api/collections/:id/videos/:videoId` - Remove a video from a collection
## Collections Feature
MyTube allows you to organize your videos into collections:
- **Create Collections**: Create custom collections to categorize your videos
- **Add to Collections**: Add videos to one or more collections directly from the video player
- **Remove from Collections**: Remove videos from collections with a single click
- **Browse Collections**: View all your collections in the sidebar and browse videos by collection
- **Create Collections**: Create custom collections to categorize your videos.
- **Add to Collections**: Add videos to one or more collections directly from the video player or manage page.
- **Remove from Collections**: Remove videos from collections easily.
- **Browse Collections**: View all your collections in the sidebar and browse videos by collection.
## User Interface
The application features a modern, dark-themed UI with:
The application features a modern, premium UI with:
- Responsive design that works on desktop and mobile devices
- Video grid layout for easy browsing
- Video player with collection management
- Author and collection filtering
- Search functionality for finding videos
- **Dark/Light Mode**: Toggle between themes to suit your preference.
- **Responsive Design**: Works seamlessly on desktop and mobile devices.
- **Video Grid**: Easy-to-browse grid layout for your video library.
- **Confirmation Modals**: Safe deletion with custom confirmation dialogs.
- **Search**: Integrated search bar for finding local and online content.
## Environment Variables
The application uses environment variables for configuration. Here's how to set them up:
The application uses environment variables for configuration.
### Frontend (.env file in frontend directory)
### Frontend (`frontend/.env`)
```
```env
VITE_API_URL=http://localhost:5551/api
VITE_BACKEND_URL=http://localhost:5551
VITE_APP_PORT=5556
```
### Backend (.env file in backend directory)
### Backend (`backend/.env`)
```
```env
PORT=5551
UPLOAD_DIR=uploads
VIDEO_DIR=uploads/videos
@@ -154,7 +157,7 @@ IMAGE_DIR=uploads/images
MAX_FILE_SIZE=500000000
```
Copy the `.env.example` files in both frontend and backend directories to create your own `.env` files and replace the placeholders with your desired values.
Copy the `.env.example` files in both frontend and backend directories to create your own `.env` files.
## Deployment

View File

@@ -5,6 +5,7 @@ import { Route, BrowserRouter as Router, Routes } from 'react-router-dom';
import './App.css';
import BilibiliPartsModal from './components/BilibiliPartsModal';
import Header from './components/Header';
import { useSnackbar } from './contexts/SnackbarContext';
import AuthorVideos from './pages/AuthorVideos';
import CollectionPage from './pages/CollectionPage';
import Home from './pages/Home';
@@ -49,6 +50,7 @@ interface BilibiliPartsInfo {
}
function App() {
const { showSnackbar } = useSnackbar();
const [videos, setVideos] = useState<Video[]>([]);
const [searchResults, setSearchResults] = useState<any[]>([]);
const [localSearchResults, setLocalSearchResults] = useState<Video[]>([]);
@@ -312,7 +314,7 @@ function App() {
}
setIsSearchMode(false);
showSnackbar('Video added successfully');
return { success: true };
} catch (err: any) {
console.error('Error downloading video:', err);
@@ -440,6 +442,7 @@ function App() {
setVideos(prevVideos => prevVideos.filter(video => video.id !== id));
setLoading(false);
showSnackbar('Video removed successfully');
return { success: true };
} catch (error) {
console.error('Error deleting video:', error);
@@ -513,7 +516,7 @@ function App() {
// Update the collections state with the new collection from the server
setCollections(prevCollections => [...prevCollections, response.data]);
showSnackbar('Collection created successfully');
return response.data;
} catch (error) {
console.error('Error creating collection:', error);
@@ -538,6 +541,7 @@ function App() {
collection.id === collectionId ? response.data : collection
));
showSnackbar('Video added to collection');
return response.data;
} catch (error) {
console.error('Error adding video to collection:', error);
@@ -567,6 +571,7 @@ function App() {
videos: collection.videos.filter(v => v !== videoId)
})));
showSnackbar('Video removed from collection');
return true;
} catch (error) {
console.error('Error removing video from collection:', error);
@@ -592,9 +597,11 @@ function App() {
await fetchVideos();
}
showSnackbar('Collection deleted successfully');
return { success: true };
} catch (error) {
console.error('Error deleting collection:', error);
showSnackbar('Failed to delete collection', 'error');
return { success: false, error: 'Failed to delete collection' };
}
};
@@ -624,7 +631,7 @@ function App() {
}
setIsSearchMode(false);
showSnackbar('Download started successfully');
return { success: true };
} catch (err: any) {
console.error('Error downloading Bilibili parts/collection:', err);

View File

@@ -0,0 +1,55 @@
import { Alert, AlertColor, Snackbar } from '@mui/material';
import React, { createContext, ReactNode, useContext, useState } from 'react';
interface SnackbarContextType {
showSnackbar: (message: string, severity?: AlertColor) => void;
}
const SnackbarContext = createContext<SnackbarContextType | undefined>(undefined);
export const useSnackbar = () => {
const context = useContext(SnackbarContext);
if (!context) {
throw new Error('useSnackbar must be used within a SnackbarProvider');
}
return context;
};
interface SnackbarProviderProps {
children: ReactNode;
}
export const SnackbarProvider: React.FC<SnackbarProviderProps> = ({ children }) => {
const [open, setOpen] = useState(false);
const [message, setMessage] = useState('');
const [severity, setSeverity] = useState<AlertColor>('success');
const showSnackbar = (msg: string, sev: AlertColor = 'success') => {
setMessage(msg);
setSeverity(sev);
setOpen(true);
};
const handleClose = (_event?: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<SnackbarContext.Provider value={{ showSnackbar }}>
{children}
<Snackbar
open={open}
autoHideDuration={3000}
onClose={handleClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert onClose={handleClose} severity={severity} sx={{ width: '100%' }} variant="filled">
{message}
</Alert>
</Snackbar>
</SnackbarContext.Provider>
);
};

View File

@@ -4,6 +4,8 @@ import App from './App';
import './index.css';
import VERSION from './version';
import { SnackbarProvider } from './contexts/SnackbarContext';
// Display version information
VERSION.displayVersion();
@@ -11,7 +13,9 @@ const rootElement = document.getElementById('root');
if (rootElement) {
createRoot(rootElement).render(
<StrictMode>
<App />
<SnackbarProvider>
<App />
</SnackbarProvider>
</StrictMode>,
);
}