feat: Add upload and scan modals on DownloadPage
This commit is contained in:
@@ -2,8 +2,10 @@ import {
|
||||
Cancel as CancelIcon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
ClearAll as ClearAllIcon,
|
||||
CloudUpload,
|
||||
Delete as DeleteIcon,
|
||||
Error as ErrorIcon,
|
||||
FindInPage,
|
||||
PlaylistAdd as PlaylistAddIcon
|
||||
} from '@mui/icons-material';
|
||||
import {
|
||||
@@ -24,6 +26,8 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import axios from 'axios';
|
||||
import React, { useState } from 'react';
|
||||
import BatchDownloadModal from '../components/BatchDownloadModal';
|
||||
import ConfirmationModal from '../components/ConfirmationModal';
|
||||
import UploadModal from '../components/UploadModal';
|
||||
import { useDownload } from '../contexts/DownloadContext';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { useSnackbar } from '../contexts/SnackbarContext';
|
||||
@@ -76,6 +80,26 @@ const DownloadPage: React.FC = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
const [showBatchModal, setShowBatchModal] = useState(false);
|
||||
const [uploadModalOpen, setUploadModalOpen] = useState(false);
|
||||
const [showScanConfirmModal, setShowScanConfirmModal] = useState(false);
|
||||
|
||||
// Scan files mutation
|
||||
const scanMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const res = await axios.post(`${API_URL}/scan-files`);
|
||||
return res.data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
showSnackbar(t('scanFilesSuccess').replace('{count}', data.addedCount.toString()) || `Scan complete. ${data.addedCount} files added.`);
|
||||
},
|
||||
onError: (error: any) => {
|
||||
showSnackbar(`${t('scanFilesFailed') || 'Scan failed'}: ${error.response?.data?.details || error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const handleBatchSubmit = async (urls: string[]) => {
|
||||
// We'll process them sequentially to be safe, or just fire them all.
|
||||
@@ -230,18 +254,44 @@ const DownloadPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', p: 2 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'flex-start', sm: 'center' },
|
||||
mb: 2,
|
||||
gap: { xs: 2, sm: 0 }
|
||||
}}>
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 0 }}>
|
||||
{t('downloads') || 'Downloads'}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<PlaylistAddIcon />}
|
||||
onClick={() => setShowBatchModal(true)}
|
||||
>
|
||||
{t('addBatchTasks') || 'Add batch tasks'}
|
||||
</Button>
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', width: { xs: '100%', sm: 'auto' } }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<FindInPage />}
|
||||
onClick={() => setShowScanConfirmModal(true)}
|
||||
disabled={scanMutation.isPending}
|
||||
>
|
||||
{scanMutation.isPending ? (t('scanning') || 'Scanning...') : (t('scanFiles') || 'Scan Files')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<PlaylistAddIcon />}
|
||||
onClick={() => setShowBatchModal(true)}
|
||||
>
|
||||
{t('addBatchTasks') || 'Add batch tasks'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<CloudUpload />}
|
||||
onClick={() => setUploadModalOpen(true)}
|
||||
>
|
||||
{t('uploadVideo') || 'Upload Video'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
@@ -404,6 +454,23 @@ const DownloadPage: React.FC = () => {
|
||||
onClose={() => setShowBatchModal(false)}
|
||||
onConfirm={handleBatchSubmit}
|
||||
/>
|
||||
<UploadModal
|
||||
open={uploadModalOpen}
|
||||
onClose={() => setUploadModalOpen(false)}
|
||||
onUploadSuccess={handleUploadSuccess}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
isOpen={showScanConfirmModal}
|
||||
onClose={() => setShowScanConfirmModal(false)}
|
||||
onConfirm={() => {
|
||||
setShowScanConfirmModal(false);
|
||||
scanMutation.mutate();
|
||||
}}
|
||||
title={t('scanFiles') || 'Scan Files'}
|
||||
message={t('scanFilesConfirmMessage') || 'The system will scan the root folder of the video path to find undocumented video files.'}
|
||||
confirmText={t('continue') || 'Continue'}
|
||||
cancelText={t('cancel') || 'Cancel'}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
ArrowBack,
|
||||
Check,
|
||||
Close,
|
||||
CloudUpload,
|
||||
|
||||
Delete,
|
||||
Edit,
|
||||
Folder,
|
||||
@@ -35,7 +35,7 @@ import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ConfirmationModal from '../components/ConfirmationModal';
|
||||
import DeleteCollectionModal from '../components/DeleteCollectionModal';
|
||||
import UploadModal from '../components/UploadModal';
|
||||
|
||||
import { useCollection } from '../contexts/CollectionContext';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { useVideo } from '../contexts/VideoContext';
|
||||
@@ -54,7 +54,7 @@ const ManagePage: React.FC = () => {
|
||||
const [isDeletingCollection, setIsDeletingCollection] = useState<boolean>(false);
|
||||
const [videoToDelete, setVideoToDelete] = useState<string | null>(null);
|
||||
const [showVideoDeleteModal, setShowVideoDeleteModal] = useState<boolean>(false);
|
||||
const [uploadModalOpen, setUploadModalOpen] = useState<boolean>(false);
|
||||
|
||||
|
||||
// Editing state
|
||||
const [editingVideoId, setEditingVideoId] = useState<string | null>(null);
|
||||
@@ -230,9 +230,7 @@ const ManagePage: React.FC = () => {
|
||||
return video.thumbnailUrl || 'https://via.placeholder.com/120x90?text=No+Thumbnail';
|
||||
};
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl" sx={{ py: 4 }}>
|
||||
@@ -241,13 +239,7 @@ const ManagePage: React.FC = () => {
|
||||
{t('manageContent')}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<CloudUpload />}
|
||||
onClick={() => setUploadModalOpen(true)}
|
||||
>
|
||||
{t('uploadVideo')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
component={Link}
|
||||
to="/"
|
||||
@@ -259,11 +251,7 @@ const ManagePage: React.FC = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<UploadModal
|
||||
open={uploadModalOpen}
|
||||
onClose={() => setUploadModalOpen(false)}
|
||||
onUploadSuccess={handleUploadSuccess}
|
||||
/>
|
||||
|
||||
|
||||
<DeleteCollectionModal
|
||||
isOpen={!!collectionToDelete}
|
||||
|
||||
@@ -127,29 +127,7 @@ const SettingsPage: React.FC = () => {
|
||||
saveMutation.mutate(settings);
|
||||
};
|
||||
|
||||
// Scan files mutation
|
||||
const scanMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const res = await axios.post(`${API_URL}/scan-files`);
|
||||
return res.data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
setInfoModal({
|
||||
isOpen: true,
|
||||
title: t('success'),
|
||||
message: t('scanFilesSuccess').replace('{count}', data.addedCount.toString()),
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
onError: (error: any) => {
|
||||
setInfoModal({
|
||||
isOpen: true,
|
||||
title: t('error'),
|
||||
message: `${t('scanFilesFailed')}: ${error.response?.data?.details || error.message}`,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Migrate data mutation
|
||||
const migrateMutation = useMutation({
|
||||
@@ -289,7 +267,7 @@ const SettingsPage: React.FC = () => {
|
||||
setSettings(prev => ({ ...prev, tags: updatedTags }));
|
||||
};
|
||||
|
||||
const isSaving = saveMutation.isPending || scanMutation.isPending || migrateMutation.isPending || cleanupMutation.isPending || deleteLegacyMutation.isPending;
|
||||
const isSaving = saveMutation.isPending || migrateMutation.isPending || cleanupMutation.isPending || deleteLegacyMutation.isPending;
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl" sx={{ py: 4 }}>
|
||||
@@ -534,15 +512,7 @@ const SettingsPage: React.FC = () => {
|
||||
{t('migrateDataButton')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => scanMutation.mutate()}
|
||||
disabled={isSaving}
|
||||
sx={{ ml: 2 }}
|
||||
>
|
||||
{t('scanFiles')}
|
||||
</Button>
|
||||
|
||||
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>{t('removeLegacyData')}</Typography>
|
||||
|
||||
@@ -59,8 +59,10 @@ export const ar = {
|
||||
migrateDataButton: "نقل البيانات من JSON",
|
||||
scanFiles: "فحص الملفات",
|
||||
scanFilesSuccess: "اكتمل الفحص. تمت إضافة {count} فيديوهات جديدة.",
|
||||
scanFilesFailed: "فشل الفحص",
|
||||
migrateConfirmation: "هل أنت متأكد أنك تريد نقل البيانات؟ قد يستغرق هذا بضع لحظات.",
|
||||
scanFilesFailed: "فشل المسح",
|
||||
scanFilesConfirmMessage: "سيقوم النظام بفحص المجلد الجذر لمسار الفيديو للعثور على ملفات الفيديو غير الموثقة.",
|
||||
scanning: "جارٍ المسح...",
|
||||
migrateConfirmation: "هل أنت متأكد أنك تريد ترحيل البيانات؟ قد يستغرق هذا بضع لحظات.",
|
||||
migrationResults: "نتائج النقل",
|
||||
migrationReport: "تقرير النقل",
|
||||
migrationSuccess: "اكتمل النقل. انظر التفاصيل في التنبيه.",
|
||||
@@ -212,6 +214,7 @@ export const ar = {
|
||||
save: "حفظ",
|
||||
on: "تشغيل",
|
||||
off: "إيقاف",
|
||||
continue: "متابعة",
|
||||
|
||||
// Video Card
|
||||
unknownDate: "تاريخ غير معروف",
|
||||
|
||||
@@ -23,6 +23,8 @@ export const de = {
|
||||
database: "Datenbank", migrateDataDescription: "Daten von Legacy-JSON-Dateien zur neuen SQLite-Datenbank migrieren. Diese Aktion kann sicher mehrmals ausgeführt werden (Duplikate werden übersprungen).",
|
||||
migrateDataButton: "Daten aus JSON migrieren", scanFiles: "Dateien Scannen",
|
||||
scanFilesSuccess: "Scan abgeschlossen. {count} neue Videos hinzugefügt.", scanFilesFailed: "Scan fehlgeschlagen",
|
||||
scanFilesConfirmMessage: "Das System scannt den Stammordner des Videopfads, um nicht dokumentierte Videodateien zu finden.",
|
||||
scanning: "Scannen...",
|
||||
migrateConfirmation: "Sind Sie sicher, dass Sie Daten migrieren möchten? Dies kann einige Momente dauern.",
|
||||
migrationResults: "Migrationsergebnisse", migrationReport: "Migrationsbericht",
|
||||
migrationSuccess: "Migration abgeschlossen. Details in der Warnung anzeigen.", migrationNoData: "Migration abgeschlossen, aber keine Daten gefunden.",
|
||||
@@ -93,7 +95,8 @@ export const de = {
|
||||
deleteCollectionTitle: "Sammlung Löschen", deleteCollectionConfirmation: "Sind Sie sicher, dass Sie die Sammlung löschen möchten",
|
||||
collectionContains: "Diese Sammlung enthält", deleteCollectionOnly: "Nur Sammlung Löschen",
|
||||
deleteCollectionAndVideos: "Sammlung und Alle Videos Löschen", loading: "Laden...", error: "Fehler",
|
||||
success: "Erfolg", cancel: "Abbrechen", confirm: "Bestätigen", save: "Speichern", on: "Ein", off: "Aus",
|
||||
success: "Erfolg", cancel: "Abbrechen", confirm: "Bestätigen", save: "Speichern", on: "Ein", off: "Aus",
|
||||
continue: "Weiter",
|
||||
unknownDate: "Unbekanntes Datum", part: "Teil", collection: "Sammlung", selectVideoFile: "Videodatei Auswählen",
|
||||
pleaseSelectVideo: "Bitte wählen Sie eine Videodatei aus", uploadFailed: "Upload fehlgeschlagen",
|
||||
failedToUpload: "Fehler beim Hochladen des Videos", uploading: "Hochladen...", upload: "Hochladen",
|
||||
|
||||
@@ -60,6 +60,8 @@ export const en = {
|
||||
scanFiles: "Scan Files",
|
||||
scanFilesSuccess: "Scan complete. Added {count} new videos.",
|
||||
scanFilesFailed: "Scan failed",
|
||||
scanFilesConfirmMessage: "The system will scan the root folder of the video path to find undocumented video files.",
|
||||
scanning: "Scanning...",
|
||||
migrateConfirmation: "Are you sure you want to migrate data? This may take a few moments.",
|
||||
migrationResults: "Migration Results",
|
||||
migrationReport: "Migration Report",
|
||||
@@ -203,6 +205,7 @@ export const en = {
|
||||
save: "Save",
|
||||
on: "On",
|
||||
off: "Off",
|
||||
continue: "Continue",
|
||||
|
||||
// Video Card
|
||||
unknownDate: "Unknown date",
|
||||
|
||||
@@ -22,7 +22,9 @@ export const es = {
|
||||
tagsManagementNote: "Recuerde hacer clic en \"Guardar Configuración\" después de agregar o eliminar etiquetas para aplicar los cambios.",
|
||||
database: "Base de Datos", migrateDataDescription: "Migrar datos de archivos JSON heredados a la nueva base de datos SQLite. Esta acción es segura para ejecutar varias veces (se omitirán duplicados).",
|
||||
migrateDataButton: "Migrar Datos desde JSON", scanFiles: "Escanear Archivos",
|
||||
scanFilesSuccess: "Escaneo completo. Se agregaron {count} nuevos videos.", scanFilesFailed: "Escaneo fallido",
|
||||
scanFilesSuccess: "Escaneo completo. Se agregaron {count} nuevos videos.", scanFilesFailed: "Escaneo fallido",
|
||||
scanFilesConfirmMessage: "El sistema escaneará la carpeta raíz de la ruta de video para encontrar archivos de video no documentados.",
|
||||
scanning: "Escaneando...",
|
||||
migrateConfirmation: "¿Está seguro de que desea migrar los datos? Esto puede tardar unos momentos.",
|
||||
migrationResults: "Resultados de Migración", migrationReport: "Informe de Migración",
|
||||
migrationSuccess: "Migración completada. Ver detalles en la alerta.", migrationNoData: "Migración finalizada pero no se encontraron datos.",
|
||||
|
||||
@@ -60,6 +60,8 @@ export const fr = {
|
||||
scanFiles: "Scanner les fichiers",
|
||||
scanFilesSuccess: "Scan terminé. {count} nouvelles vidéos ajoutées.",
|
||||
scanFilesFailed: "Échec du scan",
|
||||
scanFilesConfirmMessage: "Le système analysera le dossier racine du chemin vidéo pour trouver des fichiers vidéo non documentés.",
|
||||
scanning: "Analyse en cours...",
|
||||
migrateConfirmation: "Êtes-vous sûr de vouloir migrer les données ? Cela peut prendre quelques instants.",
|
||||
migrationResults: "Résultats de la migration",
|
||||
migrationReport: "Rapport de migration",
|
||||
|
||||
@@ -60,6 +60,8 @@ export const ja = {
|
||||
scanFiles: "ファイルをスキャン",
|
||||
scanFilesSuccess: "スキャンが完了しました。{count}個の新しい動画を追加しました。",
|
||||
scanFilesFailed: "スキャンに失敗しました",
|
||||
scanFilesConfirmMessage: "システムはビデオパスのルートフォルダをスキャンして、未登録のビデオファイルを検索します。",
|
||||
scanning: "スキャン中...",
|
||||
migrateConfirmation: "データを移行してもよろしいですか?これには時間がかかる場合があります。",
|
||||
migrationResults: "移行結果",
|
||||
migrationReport: "移行レポート",
|
||||
@@ -212,6 +214,7 @@ export const ja = {
|
||||
save: "保存",
|
||||
on: "オン",
|
||||
off: "オフ",
|
||||
continue: "続行",
|
||||
|
||||
// Video Card
|
||||
unknownDate: "不明な日付",
|
||||
|
||||
@@ -60,6 +60,8 @@ export const ko = {
|
||||
scanFiles: "파일 스캔",
|
||||
scanFilesSuccess: "스캔 완료. {count}개의 새 동영상이 추가되었습니다.",
|
||||
scanFilesFailed: "스캔 실패",
|
||||
scanFilesConfirmMessage: "시스템이 비디오 경로의 루트 폴더를 스캔하여 문서화되지 않은 비디오 파일을 찾습니다.",
|
||||
scanning: "스캔 중...",
|
||||
migrateConfirmation: "데이터를 마이그레이션하시겠습니까? 잠시 시간이 걸릴 수 있습니다.",
|
||||
migrationResults: "마이그레이션 결과",
|
||||
migrationReport: "마이그레이션 보고서",
|
||||
@@ -212,6 +214,7 @@ export const ko = {
|
||||
save: "저장",
|
||||
on: "켜기",
|
||||
off: "끄기",
|
||||
continue: "계속",
|
||||
|
||||
// Video Card
|
||||
unknownDate: "알 수 없는 날짜",
|
||||
|
||||
@@ -59,7 +59,9 @@ export const pt = {
|
||||
migrateDataButton: "Migrar Dados do JSON",
|
||||
scanFiles: "Escanear Arquivos",
|
||||
scanFilesSuccess: "Escaneamento completo. {count} novos vídeos adicionados.",
|
||||
scanFilesFailed: "Falha no escaneamento",
|
||||
scanFilesFailed: "A verificação falhou",
|
||||
scanFilesConfirmMessage: "O sistema verificará a pasta raiz do caminho do vídeo para encontrar arquivos de vídeo não documentados.",
|
||||
scanning: "Verificando...",
|
||||
migrateConfirmation: "Tem certeza de que deseja migrar os dados? Isso pode levar alguns instantes.",
|
||||
migrationResults: "Resultados da Migração",
|
||||
migrationReport: "Relatório de Migração",
|
||||
@@ -211,6 +213,7 @@ export const pt = {
|
||||
save: "Salvar",
|
||||
on: "Ligado",
|
||||
off: "Desligado",
|
||||
continue: "Continuar",
|
||||
|
||||
// Video Card
|
||||
unknownDate: "Data desconhecida",
|
||||
|
||||
@@ -59,7 +59,9 @@ export const ru = {
|
||||
migrateDataButton: "Перенести данные из JSON",
|
||||
scanFiles: "Сканировать файлы",
|
||||
scanFilesSuccess: "Сканирование завершено. Добавлено {count} новых видео.",
|
||||
scanFilesFailed: "Ошибка сканирования",
|
||||
scanFilesFailed: "Сканирование не удалось",
|
||||
scanFilesConfirmMessage: "Система просканирует корневую папку с видео, чтобы найти недовкументированные видеофайлы.",
|
||||
scanning: "Сканирование...",
|
||||
migrateConfirmation: "Вы уверены, что хотите перенести данные? Это может занять некоторое время.",
|
||||
migrationResults: "Результаты миграции",
|
||||
migrationReport: "Отчет о миграции",
|
||||
@@ -211,7 +213,8 @@ export const ru = {
|
||||
confirm: "Подтвердить",
|
||||
save: "Сохранить",
|
||||
on: "Вкл.",
|
||||
off: "Выкл.",
|
||||
off: "Выкл",
|
||||
continue: "Продолжить",
|
||||
|
||||
// Video Card
|
||||
unknownDate: "Неизвестная дата",
|
||||
|
||||
@@ -60,7 +60,9 @@ export const zh = {
|
||||
scanFiles: "扫描文件",
|
||||
scanFilesSuccess: "扫描完成。添加了 {count} 个新视频。",
|
||||
scanFilesFailed: "扫描失败",
|
||||
migrateConfirmation: "确定要迁移数据吗?这可能需要一些时间。",
|
||||
scanFilesConfirmMessage: "系统将扫描视频路径的根文件夹以查找未记录的视频文件。",
|
||||
scanning: "扫描中...",
|
||||
migrateConfirmation: "您确定要迁移数据吗?这可能需要一些时间。",
|
||||
migrationResults: "迁移结果",
|
||||
migrationReport: "迁移报告",
|
||||
migrationSuccess: "迁移完成。请查看警报中的详细信息。",
|
||||
@@ -211,7 +213,8 @@ export const zh = {
|
||||
confirm: "确认",
|
||||
save: "保存",
|
||||
on: "开启",
|
||||
off: "关闭",
|
||||
off: "关",
|
||||
continue: "继续",
|
||||
|
||||
// Video Card
|
||||
unknownDate: "未知日期",
|
||||
|
||||
Reference in New Issue
Block a user