This commit is contained in:
pika 2025-03-23 00:40:29 +01:00
parent eb93961967
commit ea3e92b8b7
10 changed files with 773 additions and 167 deletions

View file

@ -2,33 +2,237 @@
{% block title %}Profile - Flask Files{% endblock %}
{% block extra_css %}
<style>
.profile-container {
max-width: 800px;
margin: 0 auto;
}
.profile-tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 1.5rem;
}
.profile-tab {
padding: 0.75rem 1.5rem;
cursor: pointer;
background: none;
border: none;
font-weight: 500;
border-bottom: 2px solid transparent;
}
.profile-tab.active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.setting-group {
margin-bottom: 2rem;
}
.setting-group h3 {
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border-color);
}
</style>
{% endblock %}
{% block content %}
<section class="profile-container">
<h2>User Profile</h2>
<div class="profile-card">
<div class="profile-header">
<div class="avatar">
{{ current_user.username[0].upper() }}
</div>
<h3>{{ current_user.username }}</h3>
</div>
<div class="profile-tabs">
<button class="profile-tab active" data-tab="profile">Profile</button>
<button class="profile-tab" data-tab="appearance">Appearance</button>
<button class="profile-tab" data-tab="security">Security</button>
</div>
<div class="profile-stats">
<div class="stat-item">
<span class="stat-value">{{ current_user.files.count() }}</span>
<span class="stat-label">Files</span>
<!-- Profile Tab -->
<div class="tab-content active" id="profile-tab">
<div class="profile-card">
<div class="profile-header">
<div class="avatar">
{{ current_user.username[0].upper() }}
</div>
<div class="user-info">
<h3>{{ current_user.username }}</h3>
<p>Member since {{ current_user.created_at.strftime('%B %Y') if current_user.created_at else
'Unknown' }}</p>
</div>
</div>
<div class="stat-item">
<span class="stat-value">{{ current_user.shares.count() }}</span>
<span class="stat-label">Shares</span>
<div class="profile-stats">
<div class="stat-item">
<span class="stat-value">{{ current_user.files.filter_by(is_folder=False).count() }}</span>
<span class="stat-label">Files</span>
</div>
<div class="stat-item">
<span class="stat-value">{{ current_user.files.filter_by(is_folder=True).count() }}</span>
<span class="stat-label">Folders</span>
</div>
<div class="stat-item">
<span class="stat-value">{{ current_user.shares.count() }}</span>
<span class="stat-label">Shares</span>
</div>
</div>
<div class="setting-group">
<h3>Edit Profile</h3>
<form id="username-form" method="POST" action="{{ url_for('auth.update_profile') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" class="form-control"
value="{{ current_user.username }}">
</div>
<div class="form-actions">
<button type="submit" class="btn primary">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
<div class="profile-actions">
<a href="#" class="btn">Change Password</a>
<a href="{{ url_for('files.browser') }}" class="btn primary">Manage Files</a>
<!-- Appearance Tab -->
<div class="tab-content" id="appearance-tab">
<div class="setting-group">
<h3>Theme Settings</h3>
<form id="theme-form" method="POST" action="{{ url_for('auth.update_preferences') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label>Default Theme</label>
<div class="theme-options">
<label class="radio-container">
<input type="radio" name="theme_preference" value="light" {% if theme_preference=='light'
%}checked{% endif %}>
<span class="radio-label">Light</span>
</label>
<label class="radio-container">
<input type="radio" name="theme_preference" value="dark" {% if theme_preference=='dark'
%}checked{% endif %}>
<span class="radio-label">Dark</span>
</label>
<label class="radio-container">
<input type="radio" name="theme_preference" value="system" {% if theme_preference=='system'
or not theme_preference %}checked{% endif %}>
<span class="radio-label">Use System Preference</span>
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn primary">Save Preferences</button>
</div>
</form>
</div>
</div>
<!-- Security Tab -->
<div class="tab-content" id="security-tab">
<div class="setting-group">
<h3>Change Password</h3>
<form id="password-form" method="POST" action="{{ url_for('auth.change_password') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="current_password">Current Password</label>
<input type="password" id="current_password" name="current_password" class="form-control" required>
</div>
<div class="form-group">
<label for="new_password">New Password</label>
<input type="password" id="new_password" name="new_password" class="form-control" required>
<div class="password-strength" id="password-strength"></div>
</div>
<div class="form-group">
<label for="confirm_password">Confirm New Password</label>
<input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
</div>
<div class="form-actions">
<button type="submit" class="btn primary">Change Password</button>
</div>
</form>
</div>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function () {
// Tab switching
const tabs = document.querySelectorAll('.profile-tab');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', function () {
const tabId = this.getAttribute('data-tab');
// Update active tab
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
// Update active content
tabContents.forEach(content => {
content.classList.remove('active');
if (content.id === `${tabId}-tab`) {
content.classList.add('active');
}
});
});
});
// Password strength meter
const passwordInput = document.getElementById('new_password');
const strengthIndicator = document.getElementById('password-strength');
if (passwordInput && strengthIndicator) {
passwordInput.addEventListener('input', function () {
const password = this.value;
let strength = 0;
if (password.length >= 8) strength += 1;
if (password.match(/[a-z]/) && password.match(/[A-Z]/)) strength += 1;
if (password.match(/\d/)) strength += 1;
if (password.match(/[^a-zA-Z\d]/)) strength += 1;
// Update the strength indicator
strengthIndicator.className = 'password-strength';
if (password.length === 0) {
strengthIndicator.textContent = '';
} else if (strength < 2) {
strengthIndicator.textContent = 'Weak';
strengthIndicator.classList.add('weak');
} else if (strength < 4) {
strengthIndicator.textContent = 'Moderate';
strengthIndicator.classList.add('moderate');
} else {
strengthIndicator.textContent = 'Strong';
strengthIndicator.classList.add('strong');
}
});
}
// Password confirmation matching
const confirmInput = document.getElementById('confirm_password');
if (passwordInput && confirmInput) {
confirmInput.addEventListener('input', function () {
if (passwordInput.value !== this.value) {
this.setCustomValidity('Passwords must match');
} else {
this.setCustomValidity('');
}
});
}
});
</script>
{% endblock %}

View file

@ -2,13 +2,88 @@
{% block title %}Dashboard - Flask Files{% endblock %}
{% block extra_css %}
<style>
.dashboard {
max-width: 1200px;
margin: 0 auto;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
cursor: pointer;
}
.section-header .toggle-icon {
transition: transform 0.3s ease;
}
.section-header.collapsed .toggle-icon {
transform: rotate(-90deg);
}
.collapsible-section {
margin-bottom: 2rem;
transition: max-height 0.5s ease;
overflow: hidden;
}
.section-content {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.section-content.collapsed {
opacity: 0;
transform: translateY(-10px);
display: none;
}
.quick-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.quick-access {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.quick-action {
background: var(--card-bg);
border-radius: var(--border-radius);
padding: 1rem;
text-align: center;
box-shadow: var(--shadow-sm);
transition: transform 0.2s, box-shadow 0.2s;
}
.quick-action:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-md);
}
.quick-action i {
font-size: 2rem;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
</style>
{% endblock %}
{% block content %}
<section class="dashboard">
<h2>Dashboard</h2>
<div class="dashboard-stats">
<div class="quick-stats">
<div class="stat-card">
<div class="stat-icon">📁</div>
<div class="stat-icon"><i class="fas fa-folder"></i></div>
<div class="stat-info">
<span class="stat-value">{{ total_folders }}</span>
<span class="stat-label">Folders</span>
@ -16,7 +91,7 @@
</div>
<div class="stat-card">
<div class="stat-icon">📄</div>
<div class="stat-icon"><i class="fas fa-file"></i></div>
<div class="stat-info">
<span class="stat-value">{{ total_files }}</span>
<span class="stat-label">Files</span>
@ -24,51 +99,135 @@
</div>
<div class="stat-card">
<div class="stat-icon">🔗</div>
<div class="stat-icon"><i class="fas fa-link"></i></div>
<div class="stat-info">
<span class="stat-value">{{ active_shares }}</span>
<span class="stat-label">Active Shares</span>
</div>
</div>
</div>
<div class="dashboard-recent">
<h3>Recent Files</h3>
{% if recent_files %}
<div class="recent-files-list">
{% for file in recent_files %}
<div class="file-item">
<div class="file-icon">
{% if file.name.endswith('.pdf') %}📕
{% elif file.name.endswith(('.jpg', '.jpeg', '.png', '.gif')) %}🖼️
{% elif file.name.endswith(('.mp3', '.wav', '.flac')) %}🎵
{% elif file.name.endswith(('.mp4', '.mov', '.avi')) %}🎬
{% elif file.name.endswith(('.doc', '.docx')) %}📘
{% elif file.name.endswith(('.xls', '.xlsx')) %}📊
{% elif file.name.endswith(('.ppt', '.pptx')) %}📙
{% elif file.name.endswith('.zip') %}📦
{% else %}📄{% endif %}
</div>
<div class="file-details">
<div class="file-name">{{ file.name }}</div>
<div class="file-meta">
<span class="file-size">{{ (file.size / 1024)|round(1) }} KB</span>
<span class="file-date">{{ file.updated_at.strftime('%b %d, %Y') }}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon"><i class="fas fa-clock"></i></div>
<div class="stat-info">
<span class="stat-value">{{ recent_activities|default(0) }}</span>
<span class="stat-label">Recent Activities</span>
</div>
{% endfor %}
</div>
{% else %}
<p class="empty-state">No files uploaded yet. <a href="{{ url_for('files.browser') }}">Upload your first
file</a>.</p>
{% endif %}
</div>
<div class="dashboard-actions">
<a href="{{ url_for('files.browser') }}" class="btn primary">Browse Files</a>
<a href="{{ url_for('files.upload') }}" class="btn">Upload Files</a>
<div class="quick-access">
<a href="{{ url_for('files.upload') }}" class="quick-action">
<i class="fas fa-cloud-upload-alt"></i>
<div>Upload Files</div>
</a>
<a href="{{ url_for('files.browser') }}" class="quick-action">
<i class="fas fa-folder-open"></i>
<div>Browse Files</div>
</a>
<a href="#" class="quick-action">
<i class="fas fa-share-alt"></i>
<div>Manage Shares</div>
</a>
<a href="{{ url_for('auth.profile') }}" class="quick-action">
<i class="fas fa-user-cog"></i>
<div>Settings</div>
</a>
</div>
<!-- Recent Files Section (Collapsible) -->
<div class="collapsible-section" id="recent-files-section">
<div class="section-header" data-target="recent-files-content">
<h3><i class="fas fa-clock"></i> Recent Files</h3>
<span class="toggle-icon"><i class="fas fa-chevron-down"></i></span>
</div>
<div class="section-content" id="recent-files-content">
{% if recent_files %}
<div class="files-grid grid-view">
{% for file in recent_files %}
<a href="{{ url_for('files.download', file_id=file.id) }}" class="file-item" data-id="{{ file.id }}">
<div class="item-icon">
<i class="fas {{ file_icon(file.mime_type, file.name) }}"></i>
</div>
<div class="item-info">
<div class="item-name">{{ file.name }}</div>
<div class="item-details">
<span class="item-size">{{ format_size(file.size) }}</span>
<span class="item-date">{{ file.updated_at.strftime('%b %d, %Y') }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<p>No files uploaded yet. <a href="{{ url_for('files.upload') }}">Upload your first file</a>.</p>
</div>
{% endif %}
</div>
</div>
<!-- Quick Folder View (Collapsible) -->
<div class="collapsible-section" id="folders-section">
<div class="section-header" data-target="folders-content">
<h3><i class="fas fa-folder"></i> My Folders</h3>
<span class="toggle-icon"><i class="fas fa-chevron-down"></i></span>
</div>
<div class="section-content" id="folders-content">
{% if root_folders %}
<div class="files-grid grid-view">
{% for folder in root_folders %}
<a href="{{ url_for('files.browser', folder_id=folder.id) }}" class="folder-item"
data-id="{{ folder.id }}">
<div class="item-icon">
<i class="fas fa-folder"></i>
</div>
<div class="item-info">
<div class="item-name">{{ folder.name }}</div>
<div class="item-details">
<span class="item-count">{{ folder.children.count() }} items</span>
<span class="item-date">{{ folder.created_at.strftime('%b %d, %Y') }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<p>No folders created yet. <a href="{{ url_for('files.browser') }}">Create your first folder</a>.</p>
</div>
{% endif %}
</div>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function () {
// Set up collapsible sections
const sectionHeaders = document.querySelectorAll('.section-header');
sectionHeaders.forEach(header => {
header.addEventListener('click', function () {
const targetId = this.getAttribute('data-target');
const content = document.getElementById(targetId);
this.classList.toggle('collapsed');
content.classList.toggle('collapsed');
// Store preference in localStorage
localStorage.setItem(`section_${targetId}`, content.classList.contains('collapsed') ? 'closed' : 'open');
});
// Check localStorage for saved preferences
const targetId = header.getAttribute('data-target');
const savedState = localStorage.getItem(`section_${targetId}`);
if (savedState === 'closed') {
header.classList.add('collapsed');
document.getElementById(targetId).classList.add('collapsed');
}
});
});
</script>
{% endblock %}

View file

@ -6,15 +6,15 @@
<section class="file-browser">
<div class="browser-header">
<h2>File Browser</h2>
<div class="browser-actions">
<a href="{{ url_for('files.upload', folder=current_folder.id if current_folder else None) }}"
<!-- <div class="browser-actions"> -->
<!-- <a href="{{ url_for('files.upload', folder=current_folder.id if current_folder else None) }}"
class="btn primary">
<i class="fas fa-cloud-upload-alt"></i> Upload
</a>
<button class="btn" id="new-folder-btn">
</a> -->
<!-- <button class="btn" id="new-folder-btn">
<i class="fas fa-folder-plus"></i> New Folder
</button>
</div>
</button> -->
<!-- </div> -->
</div>
<div class="path-nav">
@ -112,10 +112,10 @@
<p>This folder is empty</p>
<p>Upload files or create a new folder to get started</p>
<div class="empty-actions">
<button class="btn primary" data-action="upload"
data-folder-id="{{ current_folder.id if current_folder else None }}">
<i class="fas fa-cloud-upload-alt"></i> Upload Files
</button>
<a href="{{ url_for('files.upload', folder=current_folder.id if current_folder else None) }}"
class="btn primary">
<i class=" fas fa-cloud-upload-alt"></i> Upload
</a>
<button class="btn" id="empty-new-folder-btn">
<i class="fas fa-folder-plus"></i> New Folder
</button>

View file

@ -116,6 +116,7 @@
const MAX_CONCURRENT_UPLOADS = 3;
const folderId = {{ parent_folder.id if parent_folder else 'null' }
};
});
// Setup event listeners
dropzone.addEventListener('dragover', function (e) {
@ -231,21 +232,21 @@
const fileIcon = getFileIcon(file.name);
fileItem.innerHTML = `
<div class="file-icon">
<i class="fas ${fileIcon}"></i>
</div>
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-path">${file.relativePath || 'No path'}</div>
<div class="file-size">${formatSize(file.size)}</div>
<div class="file-progress">
<div class="progress-bar-small" style="width: 0%"></div>
<div class="file-icon">
<i class="fas ${fileIcon}"></i>
</div>
</div>
<div class="file-status">
<span class="status-indicator queued">Queued</span>
</div>
`;
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-path">${file.relativePath || 'No path'}</div>
<div class="file-size">${formatSize(file.size)}</div>
<div class="file-progress">
<div class="progress-bar-small" style="width: 0%"></div>
</div>
</div>
<div class="file-status">
<span class="status-indicator queued">Queued</span>
</div>
`;
uploadList.appendChild(fileItem);
}
@ -466,9 +467,9 @@
const alert = document.createElement('div');
alert.className = `alert ${type || 'info'}`;
alert.innerHTML = `
<div class="alert-content">${message}</div>
<button class="close" aria-label="Close">&times;</button>
`;
<div class="alert-content">${message}</div>
<button class="close" aria-label="Close">&times;</button>
`;
// Add to container
alertsContainer.appendChild(alert);
@ -558,6 +559,6 @@
return 'fa-file';
}
});
});
</script>
{% endblock %}