618 lines
No EOL
16 KiB
HTML
618 lines
No EOL
16 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}File Browser - Flask Files{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.browser-container {
|
|
background: var(--card-bg);
|
|
border-radius: var(--border-radius);
|
|
padding: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.browser-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.browser-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.browser-actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
}
|
|
|
|
.breadcrumbs {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 1.5rem;
|
|
background: var(--bg-light);
|
|
padding: 0.5rem 1rem;
|
|
border-radius: var(--border-radius-sm);
|
|
}
|
|
|
|
.breadcrumb-item {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.breadcrumb-separator {
|
|
margin: 0 0.5rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.view-toggle {
|
|
display: flex;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--border-radius-sm);
|
|
overflow: hidden;
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.view-btn {
|
|
border: none;
|
|
background: var(--card-bg);
|
|
padding: 0.5rem;
|
|
cursor: pointer;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.view-btn.active {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
}
|
|
|
|
.filter-bar {
|
|
display: flex;
|
|
margin-bottom: 1rem;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.search-bar {
|
|
flex-grow: 1;
|
|
position: relative;
|
|
}
|
|
|
|
.search-bar input {
|
|
width: 100%;
|
|
padding: 0.5rem 1rem 0.5rem 2.5rem;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--border-radius-sm);
|
|
background: var(--bg-light);
|
|
}
|
|
|
|
.search-icon {
|
|
position: absolute;
|
|
left: 0.75rem;
|
|
top: 0.75rem;
|
|
color: var(--text-muted);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.sort-dropdown {
|
|
position: relative;
|
|
}
|
|
|
|
.sort-dropdown-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.5rem 1rem;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--border-radius-sm);
|
|
background: var(--bg-light);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.sort-dropdown-menu {
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 0;
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--border-radius-sm);
|
|
min-width: 200px;
|
|
z-index: 10;
|
|
box-shadow: var(--shadow-md);
|
|
display: none;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.sort-dropdown-menu.show {
|
|
display: block;
|
|
}
|
|
|
|
.sort-option {
|
|
padding: 0.5rem 1rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.sort-option:hover {
|
|
background: var(--bg-hover);
|
|
}
|
|
|
|
.sort-option.active {
|
|
color: var(--primary-color);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.files-container {
|
|
min-height: 200px;
|
|
position: relative;
|
|
}
|
|
|
|
.loading-indicator {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(var(--card-bg-rgb), 0.7);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 5;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.loading-indicator.show {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 4px solid rgba(var(--primary-color-rgb), 0.3);
|
|
border-radius: 50%;
|
|
border-top-color: var(--primary-color);
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
/* Grid view */
|
|
.files-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.folder-item,
|
|
.file-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 1rem;
|
|
border-radius: var(--border-radius-sm);
|
|
border: 1px solid var(--border-color);
|
|
transition: all 0.2s ease;
|
|
position: relative;
|
|
text-decoration: none;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.folder-item:hover,
|
|
.file-item:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-sm);
|
|
background: var(--bg-hover);
|
|
}
|
|
|
|
.item-icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 0.75rem;
|
|
text-align: center;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.folder-item .item-icon {
|
|
color: #ffc107;
|
|
}
|
|
|
|
.item-info {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.item-name {
|
|
font-weight: 500;
|
|
margin-bottom: 0.25rem;
|
|
word-break: break-word;
|
|
text-align: center;
|
|
}
|
|
|
|
.item-details {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 0.85rem;
|
|
color: var(--text-muted);
|
|
text-align: center;
|
|
}
|
|
|
|
/* List view */
|
|
.files-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.list-view .folder-item,
|
|
.list-view .file-item {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
padding: 0.75rem 1rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.list-view .item-icon {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 0;
|
|
margin-right: 1rem;
|
|
width: 1.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.list-view .item-info {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.list-view .item-name {
|
|
margin-bottom: 0;
|
|
text-align: left;
|
|
}
|
|
|
|
.list-view .item-details {
|
|
margin-left: auto;
|
|
min-width: 200px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.list-view .item-date {
|
|
margin-left: 1rem;
|
|
}
|
|
|
|
/* Empty folder state */
|
|
.empty-folder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 3rem 1rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.empty-icon {
|
|
font-size: 3rem;
|
|
color: var(--text-muted);
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.empty-message h3 {
|
|
margin-bottom: 0.5rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.empty-message p {
|
|
color: var(--text-muted);
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.empty-actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
/* Context menu */
|
|
.context-menu {
|
|
position: fixed;
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--border-radius-sm);
|
|
box-shadow: var(--shadow-md);
|
|
z-index: 100;
|
|
min-width: 180px;
|
|
padding: 0.5rem 0;
|
|
}
|
|
|
|
.context-menu-item {
|
|
padding: 0.5rem 1rem;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.context-menu-item:hover {
|
|
background: var(--bg-hover);
|
|
}
|
|
|
|
.context-menu-item.danger {
|
|
color: var(--danger-color);
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.browser-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.browser-actions {
|
|
width: 100%;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.filter-bar {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.files-grid {
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
}
|
|
|
|
.list-view .item-info {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.list-view .item-details {
|
|
margin-left: 0;
|
|
margin-top: 0.25rem;
|
|
}
|
|
}
|
|
|
|
/* Modal fixes - ensure they're hidden by default */
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
z-index: 1000;
|
|
overflow: auto;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.modal.visible {
|
|
display: flex;
|
|
}
|
|
|
|
.modal-content {
|
|
background: var(--card-bg);
|
|
border-radius: var(--border-radius);
|
|
width: 100%;
|
|
max-width: 500px;
|
|
box-shadow: var(--shadow-lg);
|
|
margin: 2rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container">
|
|
<div class="browser-container">
|
|
<div class="browser-header">
|
|
<div class="browser-title">
|
|
<i class="fas fa-folder-open"></i>
|
|
<h2>{% if current_folder %}{{ current_folder.name }}{% else %}My Files{% endif %}</h2>
|
|
</div>
|
|
<div class="browser-actions">
|
|
<input type="text" id="search-input" class="search-input" placeholder="Search files...">
|
|
<button id="search-btn" class="btn">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
<a href="{% if current_folder %}{{ url_for('files.upload', folder_id=current_folder.id) }}{% else %}{{ url_for('files.upload') }}{% endif %}"
|
|
class="btn primary">
|
|
<i class="fas fa-upload"></i> Upload
|
|
</a>
|
|
<button id="new-folder-btn" class="btn secondary">
|
|
<i class="fas fa-folder-plus"></i> New Folder
|
|
</button>
|
|
<div class="view-toggle">
|
|
<button id="grid-view-btn" class="view-btn active" title="Grid View">
|
|
<i class="fas fa-th"></i>
|
|
</button>
|
|
<button id="list-view-btn" class="view-btn" title="List View">
|
|
<i class="fas fa-list"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="breadcrumbs">
|
|
<a href="{{ url_for('files.browser') }}" class="breadcrumb-item">
|
|
<i class="fas fa-home"></i> Home
|
|
</a>
|
|
{% if breadcrumbs %}
|
|
{% for folder in breadcrumbs %}
|
|
<span class="breadcrumb-separator">/</span>
|
|
<a href="{{ url_for('files.browser', folder_id=folder.id) }}" class="breadcrumb-item">
|
|
{{ folder.name }}
|
|
</a>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div id="files-container" class="files-container grid-view">
|
|
{% include 'files/partials/folder_contents.html' %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New Folder Modal -->
|
|
<div id="new-folder-modal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3>Create New Folder</h3>
|
|
<button class="modal-close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="new-folder-form" action="{{ url_for('files.create_folder') }}" method="post">
|
|
<div class="form-group">
|
|
<label for="folder-name">Folder Name</label>
|
|
<input type="text" id="folder-name" name="name" required>
|
|
<input type="hidden" id="parent-folder-id" name="parent_id"
|
|
value="{% if current_folder %}{{ current_folder.id }}{% endif %}">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="button" class="btn secondary modal-cancel">Cancel</button>
|
|
<button type="submit" class="btn primary">Create Folder</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- File Actions Modal -->
|
|
<div id="file-actions-modal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 id="file-name-header">File Actions</h3>
|
|
<button class="modal-close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="file-actions-list">
|
|
<a id="download-action" href="#" class="file-action">
|
|
<i class="fas fa-download"></i> Download
|
|
</a>
|
|
<a id="share-action" href="#" class="file-action">
|
|
<i class="fas fa-share-alt"></i> Share
|
|
</a>
|
|
<button id="rename-action" class="file-action">
|
|
<i class="fas fa-edit"></i> Rename
|
|
</button>
|
|
<button id="delete-action" class="file-action dangerous">
|
|
<i class="fas fa-trash-alt"></i> Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rename Modal -->
|
|
<div id="rename-modal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3>Rename Item</h3>
|
|
<button class="modal-close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="rename-form">
|
|
<div class="form-group">
|
|
<label for="new-name">New Name</label>
|
|
<input type="text" id="new-name" name="name" required>
|
|
<input type="hidden" id="rename-item-id" name="item_id">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="button" class="btn secondary modal-cancel">Cancel</button>
|
|
<button type="submit" class="btn primary">Rename</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Confirmation Modal -->
|
|
<div id="delete-modal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3>Confirm Deletion</h3>
|
|
<button class="modal-close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p id="delete-confirmation-message">Are you sure you want to delete this item? This action cannot be undone.
|
|
</p>
|
|
<div class="form-actions">
|
|
<button class="btn secondary modal-cancel">Cancel</button>
|
|
<button id="confirm-delete" class="btn dangerous">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Setup modals on page load
|
|
setupModals();
|
|
|
|
// Initialize variables
|
|
const filesContainer = document.getElementById('files-container');
|
|
const gridViewBtn = document.getElementById('grid-view-btn');
|
|
const listViewBtn = document.getElementById('list-view-btn');
|
|
const newFolderBtn = document.getElementById('new-folder-btn');
|
|
let selectedItemId = null;
|
|
|
|
// Button event listeners
|
|
if (gridViewBtn && listViewBtn) {
|
|
gridViewBtn.addEventListener('click', function () {
|
|
filesContainer.className = 'files-container grid-view';
|
|
gridViewBtn.classList.add('active');
|
|
listViewBtn.classList.remove('active');
|
|
|
|
// Save preference
|
|
localStorage.setItem('view_preference', 'grid');
|
|
});
|
|
|
|
listViewBtn.addEventListener('click', function () {
|
|
filesContainer.className = 'files-container list-view';
|
|
listViewBtn.classList.add('active');
|
|
gridViewBtn.classList.remove('active');
|
|
|
|
// Save preference
|
|
localStorage.setItem('view_preference', 'list');
|
|
});
|
|
}
|
|
|
|
// Apply saved view preference
|
|
const savedView = localStorage.getItem('view_preference');
|
|
if (savedView === 'list') {
|
|
filesContainer.className = 'files-container list-view';
|
|
if (listViewBtn && gridViewBtn) {
|
|
listViewBtn.classList.add('active');
|
|
gridViewBtn.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// New folder button
|
|
if (newFolderBtn) {
|
|
newFolderBtn.addEventListener('click', function () {
|
|
openModal('new-folder-modal');
|
|
document.getElementById('folder-name').focus();
|
|
});
|
|
}
|
|
|
|
// Setup file item event listeners
|
|
function setupFileListeners() {
|
|
// ... your existing file listeners ...
|
|
}
|
|
|
|
// Initial setup
|
|
setupFileListeners();
|
|
});
|
|
</script>
|
|
{% endblock %} |