|
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% block title %}CloudStorage{% endblock %}</title>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
|
<style>
|
|
:root {
|
|
--bs-primary: {{ session.get('color_scheme', '#0d6efd') }};
|
|
--bs-primary-rgb: {{ session.get('color_scheme', '#0d6efd')|hex_to_rgb }};
|
|
}
|
|
.btn-primary {
|
|
background-color: var(--bs-primary);
|
|
border-color: var(--bs-primary);
|
|
}
|
|
.bg-primary {
|
|
background-color: var(--bs-primary) !important;
|
|
}
|
|
.text-primary {
|
|
color: var(--bs-primary) !important;
|
|
}
|
|
/* Исправление проблемы с перекрытием элементов */
|
|
.modal-backdrop {
|
|
z-index: 1040 !important;
|
|
}
|
|
.modal {
|
|
z-index: 1050 !important;
|
|
}
|
|
.dropdown-menu {
|
|
z-index: 1060 !important;
|
|
}
|
|
</style>
|
|
{% block head %}{% endblock %}
|
|
</head>
|
|
<body class="bg-image" style="background-image: url('{{ url_for('static', filename='backgrounds/' + background) }}');">
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
|
<div class="container">
|
|
<a class="navbar-brand" href="{{ url_for('index') }}">CloudStorage</a>
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="navbarNav">
|
|
<ul class="navbar-nav me-auto">
|
|
{% if session.user_id %}
|
|
<li class="nav-item dropdown">
|
|
<a class="nav-link dropdown-toggle" href="#" id="filesDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
Мои файлы
|
|
</a>
|
|
<ul class="dropdown-menu" aria-labelledby="filesDropdown">
|
|
<li><a class="dropdown-item" href="{{ url_for('dashboard') }}">Мои файлы</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('shared_with_me') }}">Доступные мне</a></li>
|
|
</ul>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.path.startswith('/contacts') %}active{% endif %}" href="{{ url_for('contacts') }}">
|
|
<i class="fas fa-address-book me-1"></i> Контакты
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.path.startswith('/messages') %}active{% endif %}" href="{{ url_for('messages', contact_id=0) if session.user_id else '#' }}">
|
|
<i class="fas fa-comments me-1"></i> Сообщения
|
|
<span id="unreadBadge" class="badge bg-danger ms-2" style="display: none;">0</span>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
<ul class="navbar-nav">
|
|
{% if session.user_id %}
|
|
<li class="nav-item dropdown">
|
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown">
|
|
{{ session.username }}
|
|
</a>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li><a class="dropdown-item" href="{{ url_for('settings') }}"><i class="fas fa-cog"></i> Настройки</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('logout') }}"><i class="fas fa-sign-out-alt"></i> Выйти</a></li>
|
|
</ul>
|
|
</li>
|
|
{% else %}
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="{{ url_for('login') }}">Войти</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="{{ url_for('register') }}">Регистрация</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container mt-4">
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ category if category != 'error' else 'danger' }} alert-dismissible fade show">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
|
|
|
|
|
|
{% if session.user_id %}
|
|
<div class="modal fade" id="changeBackgroundModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Изменить фоновое изображение</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form action="{{ url_for('change_background') }}" method="post" enctype="multipart/form-data">
|
|
<div class="mb-3">
|
|
<label for="background_file" class="form-label">Выберите изображение</label>
|
|
<input type="file" class="form-control" id="background_file" name="background_file" accept="image/*" required>
|
|
</div>
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-primary">Загрузить и применить</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const colorSchemeSelect = document.getElementById('color_scheme');
|
|
if (colorSchemeSelect) {
|
|
colorSchemeSelect.value = '{{ session.get("color_scheme", "#0d6efd") }}';
|
|
colorSchemeSelect.addEventListener('change', function() {
|
|
document.documentElement.style.setProperty('--bs-primary', this.value);
|
|
document.documentElement.style.setProperty('--bs-primary-rgb', this.value.replace('#', '').match(/.{2}/g).join(', '));
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
{% if session.user_id %}
|
|
<script>
|
|
|
|
function updateUnreadCount() {
|
|
fetch('/get_unread_count')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const unreadBadge = document.getElementById('unreadBadge');
|
|
if (unreadBadge) {
|
|
|
|
const currentCount = parseInt(unreadBadge.getAttribute('data-count') || '0');
|
|
const newCount = data.unread_count;
|
|
|
|
|
|
if (newCount > 0) {
|
|
unreadBadge.textContent = newCount;
|
|
unreadBadge.style.display = 'inline';
|
|
} else {
|
|
unreadBadge.style.display = 'none';
|
|
}
|
|
|
|
|
|
if (newCount > currentCount && currentCount >= 0) {
|
|
showMessageNotification(newCount - currentCount);
|
|
}
|
|
|
|
|
|
unreadBadge.setAttribute('data-count', newCount);
|
|
}
|
|
})
|
|
.catch(error => console.error('Ошибка при обновлении счетчика:', error));
|
|
}
|
|
|
|
|
|
function playNotificationSound() {
|
|
const audio = new Audio('{{ url_for("static", filename="sounds/notification.mp3") }}');
|
|
audio.play().catch(error => console.error('Ошибка воспроизведения звука:', error));
|
|
}
|
|
|
|
|
|
async function requestNotificationPermission() {
|
|
try {
|
|
const permission = await Notification.requestPermission();
|
|
return permission === 'granted';
|
|
} catch (error) {
|
|
console.error('Ошибка при запросе разрешения на уведомления:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
async function showMessageNotification(count) {
|
|
|
|
playNotificationSound();
|
|
|
|
|
|
if (!('Notification' in window)) {
|
|
console.log('Этот браузер не поддерживает уведомления');
|
|
showToastNotification(count);
|
|
return;
|
|
}
|
|
|
|
|
|
if (Notification.permission !== 'granted') {
|
|
const permissionGranted = await requestNotificationPermission();
|
|
if (!permissionGranted) {
|
|
showToastNotification(count);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
const title = 'Новое сообщение';
|
|
const options = {
|
|
body: `У вас ${count} ${count === 1 ? 'новое сообщение' : 'новых сообщений'}!`,
|
|
icon: '{{ url_for("static", filename="favicon.ico") }}',
|
|
badge: '{{ url_for("static", filename="favicon.ico") }}',
|
|
tag: 'new-message',
|
|
requireInteraction: true
|
|
};
|
|
|
|
try {
|
|
const notification = new Notification(title, options);
|
|
notification.onclick = function() {
|
|
window.focus();
|
|
window.location.href = '{{ url_for("messages", contact_id=0) }}';
|
|
notification.close();
|
|
};
|
|
} catch (error) {
|
|
console.error('Ошибка при создании уведомления:', error);
|
|
showToastNotification(count);
|
|
}
|
|
}
|
|
|
|
|
|
function showToastNotification(count) {
|
|
|
|
const notification = document.createElement('div');
|
|
notification.className = 'toast show';
|
|
notification.style.position = 'fixed';
|
|
notification.style.bottom = '20px';
|
|
notification.style.right = '20px';
|
|
notification.style.minWidth = '250px';
|
|
notification.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
|
|
notification.style.boxShadow = '0 0.5rem 1rem rgba(0, 0, 0, 0.15)';
|
|
notification.style.borderRadius = '0.25rem';
|
|
notification.style.zIndex = '9999';
|
|
|
|
|
|
notification.innerHTML = `
|
|
<div class="toast-header bg-primary text-white">
|
|
<i class="fas fa-envelope me-2"></i>
|
|
<strong class="me-auto">Новое сообщение</strong>
|
|
<button type="button" class="btn-close btn-close-white" onclick="this.parentNode.parentNode.remove()"></button>
|
|
</div>
|
|
<div class="toast-body">
|
|
<p>У вас ${count} ${count === 1 ? 'новое сообщение' : 'новых сообщений'}!</p>
|
|
<div class="mt-2 pt-2 border-top">
|
|
<a href="{{ url_for('messages', contact_id=0) }}" class="btn btn-primary btn-sm">Перейти к сообщениям</a>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
|
|
setTimeout(() => {
|
|
notification.remove();
|
|
}, 5000);
|
|
}
|
|
|
|
|
|
async function checkNotificationPermission() {
|
|
if ('Notification' in window) {
|
|
if (Notification.permission !== 'granted' && Notification.permission !== 'denied') {
|
|
|
|
await requestNotificationPermission();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
const unreadBadge = document.getElementById('unreadBadge');
|
|
if (unreadBadge) {
|
|
unreadBadge.setAttribute('data-count', '0');
|
|
}
|
|
|
|
|
|
checkNotificationPermission();
|
|
|
|
updateUnreadCount();
|
|
|
|
setInterval(updateUnreadCount, 15000);
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
</body>
|
|
</html> |