Spaces:
Sleeping
Sleeping
| {% extends "base.html" %} | |
| {% block title %}Drive Manager{% endblock %} | |
| {% block styles %} | |
| <style> | |
| .source-card { | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .source-card:hover { | |
| transform: translateY(-5px); | |
| border-color: #0d6efd ; | |
| box-shadow: 0 8px 16px rgba(13, 110, 253, 0.3); | |
| } | |
| .recent-pdf-card:hover { | |
| transform: translateY(-3px) scale(1.02); | |
| box-shadow: 0 6px 20px rgba(13, 110, 253, 0.4); | |
| } | |
| </style> | |
| {% endblock %} | |
| {% block content %} | |
| <div class="container-fluid mt-4" style="width: 90%; margin: auto;"> | |
| <div class="d-flex justify-content-between align-items-center mb-4"> | |
| <h1>Drive Sync Manager</h1> | |
| <div> | |
| {% if not drive_connected %} | |
| <a href="/drive/connect" class="btn btn-outline-warning me-2"> | |
| <i class="bi bi-google me-1"></i> Connect Drive | |
| </a> | |
| {% endif %} | |
| <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addSourceModal"> | |
| <i class="bi bi-plus-lg me-1"></i> Add Drive Source | |
| </button> | |
| </div> | |
| </div> | |
| {% if recent_pdfs %} | |
| <div class="card border-secondary mb-4" style="background: linear-gradient(135deg, #1a1d20 0%, #212529 100%);"> | |
| <div class="card-header border-secondary" style="background: linear-gradient(90deg, rgba(13,110,253,0.15) 0%, rgba(13,110,253,0.05) 100%);"> | |
| <h5 class="mb-0 text-primary"><i class="bi bi-clock-history me-2"></i>Recently Opened PDFs</h5> | |
| </div> | |
| <div class="card-body"> | |
| <div class="row row-cols-1 row-cols-md-2 row-cols-lg-4 g-3"> | |
| {% for pdf in recent_pdfs %} | |
| <div class="col"> | |
| <div class="card h-100 border-primary source-card" onclick="location.href='/drive/api/open/{{ pdf.file_id }}'" style="background: linear-gradient(135deg, #212529 0%, #2b3035 100%); transition: all 0.3s ease;"> | |
| <div class="card-body d-flex flex-column"> | |
| <div class="mb-2 flex-grow-1"> | |
| <h6 class="card-title text-primary mb-2" style="word-wrap: break-word; line-height: 1.4;"> | |
| <i class="bi bi-file-pdf-fill me-2"></i>{{ pdf.filename }} | |
| </h6> | |
| </div> | |
| <div class="mt-auto pt-2 border-top border-secondary"> | |
| <small class="text-muted"> | |
| <i class="bi bi-clock me-1"></i>{{ pdf.opened_at }} | |
| </small> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| </div> | |
| {% endif %} | |
| {% if drive_connected %} | |
| <div class="card bg-dark text-white border-warning mb-4 source-card" onclick="location.href='/drive/api/browse/root'"> | |
| <div class="card-body d-flex justify-content-between align-items-center"> | |
| <div> | |
| <h5 class="card-title text-warning mb-0"><i class="bi bi-google me-2"></i>My Drive</h5> | |
| <small class="text-muted">Browse your personal Google Drive (API)</small> | |
| </div> | |
| <i class="bi bi-chevron-right text-muted"></i> | |
| </div> | |
| </div> | |
| {% endif %} | |
| <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4" id="sources-grid"> | |
| {% for source in sources %} | |
| <div class="col"> | |
| <div class="card h-100 bg-dark text-white border-secondary source-card"> | |
| <div class="card-body" onclick="location.href='/drive/browse/{{ source.id }}'"> | |
| <div class="d-flex justify-content-between align-items-start mb-2"> | |
| <h5 class="card-title text-primary"> | |
| {% if source.source_type == 'file' %} | |
| <i class="bi bi-file-earmark-arrow-down me-2"></i> | |
| {% else %} | |
| <i class="bi bi-hdd-network me-2"></i> | |
| {% endif %} | |
| {{ source.name }} | |
| </h5> | |
| <div class="dropdown" onclick="event.stopPropagation()"> | |
| <button class="btn btn-link text-muted p-0" data-bs-toggle="dropdown"> | |
| <i class="bi bi-three-dots-vertical"></i> | |
| </button> | |
| <ul class="dropdown-menu dropdown-menu-dark"> | |
| <li><button class="dropdown-item sync-btn" data-id="{{ source.id }}"><i class="bi bi-arrow-repeat me-2"></i>Sync Now</button></li> | |
| <li><hr class="dropdown-divider"></li> | |
| <li><button class="dropdown-item text-danger delete-btn" data-id="{{ source.id }}"><i class="bi bi-trash me-2"></i>Delete</button></li> | |
| </ul> | |
| </div> | |
| </div> | |
| <p class="card-text text-muted small text-truncate">{{ source.url }}</p> | |
| <div class="mt-3"> | |
| <small class="text-secondary"> | |
| Last Synced: {{ source.last_synced or 'Never' }} | |
| </small> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% else %} | |
| <div class="col-12 text-center text-muted py-5"> | |
| <i class="bi bi-cloud-slash display-4 mb-3 d-block"></i> | |
| <p>No drive sources added yet.</p> | |
| </div> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| <!-- Add Modal --> | |
| <div class="modal fade" id="addSourceModal" tabindex="-1"> | |
| <div class="modal-dialog"> | |
| <div class="modal-content bg-dark text-white"> | |
| <div class="modal-header"> | |
| <h5 class="modal-title">Add Public Drive Folder</h5> | |
| <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button> | |
| </div> | |
| <div class="modal-body"> | |
| <form id="add-source-form"> | |
| <div class="mb-3"> | |
| <label class="form-label">Name (Local Folder Name)</label> | |
| <input type="text" class="form-control" name="name" required placeholder="e.g. Question Papers 2024"> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="form-label">Drive Folder URL</label> | |
| <input type="url" class="form-control" name="url" required placeholder="https://drive.google.com/drive/folders/..."> | |
| <div class="form-text text-muted">Must be a publicly accessible Google Drive folder link.</div> | |
| </div> | |
| </form> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> | |
| <button type="button" class="btn btn-primary" id="save-source-btn">Add Source</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block scripts %} | |
| <script> | |
| document.getElementById('save-source-btn').addEventListener('click', async () => { | |
| const form = document.getElementById('add-source-form'); | |
| if (!form.checkValidity()) { | |
| form.reportValidity(); | |
| return; | |
| } | |
| const formData = new FormData(form); | |
| try { | |
| const res = await fetch('/drive/add', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await res.json(); | |
| if (data.success) { | |
| location.reload(); | |
| } else { | |
| alert('Error: ' + data.error); | |
| } | |
| } catch (e) { | |
| alert('Error adding source'); | |
| } | |
| }); | |
| document.querySelectorAll('.sync-btn').forEach(btn => { | |
| btn.addEventListener('click', async () => { | |
| const id = btn.dataset.id; | |
| if(!confirm('Start sync in background? Large folders may take time.')) return; | |
| try { | |
| const res = await fetch(`/drive/sync/${id}`, { method: 'POST' }); | |
| const data = await res.json(); | |
| if (data.success) alert('Sync started!'); | |
| else alert('Error: ' + data.error); | |
| } catch (e) { | |
| alert('Request failed'); | |
| } | |
| }); | |
| }); | |
| document.querySelectorAll('.delete-btn').forEach(btn => { | |
| btn.addEventListener('click', async () => { | |
| const id = btn.dataset.id; | |
| if(!confirm('Delete this source and all downloaded files locally?')) return; | |
| try { | |
| const res = await fetch(`/drive/delete/${id}`, { method: 'POST' }); | |
| const data = await res.json(); | |
| if (data.success) location.reload(); | |
| else alert('Error: ' + data.error); | |
| } catch (e) { | |
| alert('Request failed'); | |
| } | |
| }); | |
| }); | |
| </script> | |
| {% endblock %} | |