feat: Add snackbar notifications for various actions
This commit is contained in:
92
README-zh.md
92
README-zh.md
@@ -6,41 +6,40 @@
|
||||
|
||||

|
||||
|
||||
|
||||
## 功能特点
|
||||
|
||||
- 通过简单的 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` 文件。
|
||||
|
||||
## 部署
|
||||
|
||||
|
||||
91
README.md
91
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
55
frontend/src/contexts/SnackbarContext.tsx
Normal file
55
frontend/src/contexts/SnackbarContext.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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>,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user