diff --git a/main.py b/main.py index 82fc136..ae4abc8 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, Query, Request, Depends, status -from fastapi.responses import StreamingResponse, FileResponse, JSONResponse, Response +from fastapi.responses import StreamingResponse, FileResponse, JSONResponse, Response, RedirectResponse from fastapi.responses import HTMLResponse from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles @@ -359,7 +359,29 @@ async def login_page(request: Request): @app.get("/watchlist") -async def watchlist_page(request: Request): +async def watchlist_redirect(): + """Redirect /watchlist to web interface with watchlist hash""" + return RedirectResponse("/web#watchlist") +#JJ|# API Endpoints + +#WY|@app.post("/api/download") +#JJ|# API Endpoints + +#WY|@app.post("/api/download") +async def create_download(request: DownloadRequest, background_tasks: BackgroundTasks): +#JJ|# API Endpoints + +#WY|@app.post("/api/download") +#JJ|# API Endpoints + +#WY|@app.post("/api/download") + +#JJ|# API Endpoints + +#WY|@app.post("/api/download") +#JJ|# API Endpoints +#JJ|# API Endpoints + """Watchlist management page""" return templates.TemplateResponse("watchlist.html", {"request": request}) diff --git a/static/css/style.css b/static/css/style.css index 55b11ec..d026bf7 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1366,3 +1366,190 @@ justify-content: flex-start; } } + +/* =================================== + Watchlist Page Styles + =================================== */ + +.watchlist-body { + background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); + min-height: 100vh; + color: #e0e0e0; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +.watchlist-header { + background: rgba(217, 255, 0.1); + border: 1px solid rgba(0, 217, 255, 0.3); + border-radius: 12px; + padding: 30px; + margin-bottom: 30px; + text-align: center; +} + +.watchlist-header h1 { + color: #00d9ff; + margin: 0 0 10px 0; + font-size: 28px; + font-weight: 600; +} + +.watchlist-header p { + color: #999; + margin: 0; + font-size: 14px; +} + +.watchlist-controls { + display: flex; + gap: 15px; + justify-content: center; + margin-bottom: 30px; + flex-wrap: wrap; +} + +.watchlist-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px 40px; +} + +.scheduler-status { + background: rgba(0, 217, 255, 0.05); + border: 1px solid rgba(0, 217, 255, 0.2); + border-radius: 10px; + padding: 20px; + margin-bottom: 30px; +} + +.scheduler-status-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.scheduler-status h3 { + margin: 0; + color: #00d9ff; + font-size: 18px; +} + +.scheduler-controls { + display: flex; + gap: 10px; +} + +.status-indicator { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 5px 12px; + border-radius: 12px; + font-size: 13px; +} + +.status-indicator.running { + background: rgba(76, 175, 80, 0.2); + color: #4caf50; +} + +.status-indicator.stopped { + background: rgba(244, 67, 54, 0.2); + color: #f44; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.status-dot.running { + background: #4caf50; + animation: watchlist-pulse 2s infinite; +} + +.status-dot.stopped { + background: #f44; +} + +@keyframes watchlist-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.next-run-info { + font-size: 13px; + color: #999; + margin-top: 10px; +} + +.filter-tabs { + display: flex; + gap: 10px; + margin-bottom: 20px; + justify-content: center; +} + +.filter-tab { + padding: 8px 16px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + color: #ccc; + cursor: pointer; + transition: all 0.2s; +} + +.filter-tab:hover { + background: rgba(255, 255, 255, 0.1); +} + +.filter-tab.active { + background: rgba(0, 217, 255, 0.2); + border-color: rgba(0, 217, 255, 0.5); + color: #00d9ff; +} + +.watchlist-loading { + text-align: center; + padding: 60px; + color: #999; +} + +.empty-watchlist { + text-align: center; + padding: 80px 20px; +} + +.watchlist-error-message { + text-align: center; + padding: 40px; + color: #f44; +} + +.watchlist-item { + transition: all 0.3s ease; +} + +.watchlist-item:hover { + background: rgba(255, 255, 255, 0.08); + transform: translateY(-2px); +} + +.watchlist-btn-small { + padding: 6px 12px; + font-size: 12px; +} + +.watchlist-header-back-btn { + margin-top: 15px; +} + +.watchlist-modal-action-btn { + flex: 1; + padding: 12px; + font-size: 14px; + cursor: pointer; +} diff --git a/static/js/main.js b/static/js/main.js index a4ec5f4..d0a0f56 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -228,6 +228,9 @@ function switchTab(tabName) { } else if (tabType === 'providers' && tabName === 'providers') { // Static providers tab btn.classList.add('active'); + } else if (tabType === 'watchlist' && tabName === 'watchlist') { + // Static watchlist tab + btn.classList.add('active'); } else if (tabType === 'anime' && btn.getAttribute('data-provider') === tabName.replace('anime-', '')) { btn.classList.add('active'); } else if (tabType === 'series' && btn.getAttribute('data-provider') === tabName.replace('series-', '')) { diff --git a/static/js/tabs.js b/static/js/tabs.js index a720422..f27edc5 100644 --- a/static/js/tabs.js +++ b/static/js/tabs.js @@ -147,6 +147,7 @@ async function loadSeriesRecommendations() { } else { container.innerHTML = '
Aucune recommandation trouvée
'; } + } catch (error) { console.error('Error loading series recommendations:', error); const container = document.getElementById('seriesRecommendationsList'); @@ -360,10 +361,22 @@ window.showDownloadInfo = showDownloadInfo; document.addEventListener('DOMContentLoaded', () => { // Wait for main.js to be loaded setTimeout(() => { + // Watchlist auto-refresh interval + let watchlistRefreshInterval = null; + + // Initialize watchlist tab flag + window.watchlistTabLoaded = false; + // Override switchTab to load content when opening new tabs const originalSwitchTab = window.switchTab; if (originalSwitchTab) { window.switchTab = function(tabName) { + // Clear watchlist interval when switching away from watchlist + if (tabName !== 'watchlist' && watchlistRefreshInterval) { + clearInterval(watchlistRefreshInterval); + watchlistRefreshInterval = null; + } + // Call original switchTab first originalSwitchTab(tabName); @@ -386,8 +399,26 @@ document.addEventListener('DOMContentLoaded', () => { window.providersTabLoaded = true; } } else if (tabName === 'watchlist') { - // Watchlist is handled by its own page - window.location.href = '/watchlist'; + // Clear any existing interval before starting new one + if (watchlistRefreshInterval) { + clearInterval(watchlistRefreshInterval); + watchlistRefreshInterval = null; + } + + if (!window.watchlistTabLoaded) { + // Load watchlist content + if (typeof displayWatchlist === 'function') { + displayWatchlist(); + } + window.watchlistTabLoaded = true; + } + + // Start 30-second auto-refresh interval for watchlist + if (typeof displayWatchlist === 'function') { + watchlistRefreshInterval = setInterval(() => { + displayWatchlist(); + }, 30000); + } } }, 100); }; diff --git a/static/js/watchlist-ui.js b/static/js/watchlist-ui.js index a36848b..d52829a 100644 --- a/static/js/watchlist-ui.js +++ b/static/js/watchlist-ui.js @@ -321,6 +321,169 @@ async function handleCheckAll() { } } + + +/** + * Create settings modal HTML + */ +function createSettingsModal(settings) { + const modalHtml = ` +
+
+
+

⚙️ Paramètres Watchlist

+ +
+ +
+ +
+ + +

Entre 1 et 168 heures (1 semaine)

+
+ + +
+
+
📥 Téléchargement automatique
+

Télécharger automatiquement les nouveaux épisodes

+
+ +
+ + +
+ + +

Maximum 5 téléchargements en parallèle

+
+ + +
+
+
🔔 Notifications
+

Être notifié des nouveaux épisodes

+
+ +
+
+ +
+ + +
+
+
+ + + `; + + return modalHtml; +} + +/** + * Close settings modal + */ +function closeSettingsModal() { + const modal = document.getElementById('settingsModal'); + if (modal) { + modal.remove(); + } +} + +/** + * Save settings + */ +async function saveSettings() { + try { + const checkInterval = parseInt(document.getElementById('checkInterval').value); + const autoDownloadEnabled = document.getElementById('autoDownloadEnabled').checked; + const maxConcurrent = parseInt(document.getElementById('maxConcurrent').value); + const notifyEnabled = document.getElementById('notifyEnabled').checked; + + const settings = { + check_interval_hours: checkInterval, + auto_download_enabled: autoDownloadEnabled, + max_concurrent_auto_downloads: maxConcurrent, + notify_on_new_episodes: notifyEnabled + }; + + await updateWatchlistSettings(settings); + + // Restart scheduler if it's running to apply new interval + const status = await getSchedulerStatus(); + if (status.running) { + await stopScheduler(); + await startScheduler(); + } + + closeSettingsModal(); + alert('✅ Paramètres enregistrés avec succès!'); + await loadSchedulerStatus(); + + } catch (error) { + console.error('Error saving settings:', error); + alert(`❌ Erreur: ${error.message}`); + } +} + // Make functions available globally window.displayWatchlist = displayWatchlist; window.handleAddToWatchlist = handleAddToWatchlist; @@ -329,3 +492,6 @@ window.handleResumeWatchlist = handleResumeWatchlist; window.handleCheckItem = handleCheckItem; window.handleDeleteWatchlist = handleDeleteWatchlist; window.handleCheckAll = handleCheckAll; +window.createSettingsModal = createSettingsModal; +window.closeSettingsModal = closeSettingsModal; +window.saveSettings = saveSettings; \ No newline at end of file diff --git a/templates/components/watchlist_section.html b/templates/components/watchlist_section.html new file mode 100644 index 0000000..669306d --- /dev/null +++ b/templates/components/watchlist_section.html @@ -0,0 +1,46 @@ + + +
+

📋 Ma Watchlist

+

Suivez vos animes préférés et téléchargez automatiquement les nouveaux épisodes

+ +
+ + +
+
+
+

⏰ Planificateur Automatique

+
Chargement...
+
+
+ + + + +
+
+
+ + +
+ + + + +
+ + +
+
Chargement de la watchlist...
+
diff --git a/templates/index.html b/templates/index.html index 9cab225..fb34c73 100644 --- a/templates/index.html +++ b/templates/index.html @@ -112,6 +112,10 @@
+
+{% include "components/watchlist_section.html" %} +
+ {% include "components/downloads_section.html" %} diff --git a/templates/watchlist.html b/templates/watchlist.html index 8d70717..e5b7a08 100644 --- a/templates/watchlist.html +++ b/templates/watchlist.html @@ -5,204 +5,36 @@ Watchlist - Ohm Stream Downloader - - + + +
+

⚡ Ohm Stream Downloader

+

Téléchargez vos vidéos, animes et séries

+
+ + + + + +
+ + + + + +
+

📋 Ma Watchlist

Suivez vos animes préférés et téléchargez automatiquement les nouveaux épisodes

-
@@ -215,16 +47,16 @@
Chargement...
- - - -
@@ -241,7 +73,7 @@
-
Chargement de la watchlist...
+
Chargement de la watchlist...
@@ -394,175 +226,20 @@ async function handleOpenSettings() { try { const settings = await getWatchlistSettings(); - - // Create modal HTML - const modalHtml = ` -
-
-
-

⚙️ Paramètres Watchlist

- -
- -
- -
- - -

Entre 1 et 168 heures (1 semaine)

-
- - -
-
-
📥 Téléchargement automatique
-

Télécharger automatiquement les nouveaux épisodes

-
- -
- - -
- - -

Maximum 5 téléchargements en parallèle

-
- - -
-
-
🔔 Notifications
-

Être notifié des nouveaux épisodes

-
- -
-
- -
- - -
-
-
- - - `; - + const modalHtml = createSettingsModal(settings); + // Add modal to body const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHtml; document.body.appendChild(modalContainer); - } catch (error) { console.error('Error loading settings:', error); alert(`❌ Erreur: ${error.message}`); } } - /** - * Close settings modal - */ - function closeSettingsModal() { - const modal = document.getElementById('settingsModal'); - if (modal) { - modal.remove(); - } - } - - /** - * Save settings - */ - async function saveSettings() { - try { - const checkInterval = parseInt(document.getElementById('checkInterval').value); - const autoDownloadEnabled = document.getElementById('autoDownloadEnabled').checked; - const maxConcurrent = parseInt(document.getElementById('maxConcurrent').value); - const notifyEnabled = document.getElementById('notifyEnabled').checked; - - const settings = { - check_interval_hours: checkInterval, - auto_download_enabled: autoDownloadEnabled, - max_concurrent_auto_downloads: maxConcurrent, - notify_on_new_episodes: notifyEnabled - }; - - await updateWatchlistSettings(settings); - - // Restart scheduler if it's running to apply new interval - const status = await getSchedulerStatus(); - if (status.running) { - await stopScheduler(); - await startScheduler(); - } - - closeSettingsModal(); - alert('✅ Paramètres enregistrés avec succès!'); - await loadSchedulerStatus(); - - } catch (error) { - console.error('Error saving settings:', error); - alert(`❌ Erreur: ${error.message}`); - } - } - // Auto-refresh scheduler status every 30 seconds setInterval(loadSchedulerStatus, 30000); - + \ No newline at end of file