- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование - Удалены дублирующие настройки navigation для чистой группировки - Добавлены CSS стили для визуального отображения иерархии с отступами - Добавлены эмодзи-иконки для каждого типа ресурсов через CSS - Улучшена навигация с правильной вложенностью элементов
953 lines
31 KiB
HTML
953 lines
31 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Менеджер изображений</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: #f8f9fa;
|
||
color: #333;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.header h1 {
|
||
color: #333;
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #f8f9fa;
|
||
color: #6c757d;
|
||
border: 1px solid #dee2e6;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #e9ecef;
|
||
}
|
||
|
||
/* Загрузка файлов */
|
||
.upload-area {
|
||
background: white;
|
||
border: 2px dashed #ddd;
|
||
border-radius: 12px;
|
||
padding: 40px;
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.upload-area:hover {
|
||
border-color: #667eea;
|
||
background: #f8f9ff;
|
||
}
|
||
|
||
.upload-area.dragover {
|
||
border-color: #667eea;
|
||
background: #f0f4ff;
|
||
transform: scale(1.02);
|
||
}
|
||
|
||
.upload-icon {
|
||
font-size: 48px;
|
||
color: #ddd;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.upload-area:hover .upload-icon {
|
||
color: #667eea;
|
||
}
|
||
|
||
.upload-text {
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.upload-subtext {
|
||
color: #6c757d;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* Фильтры и поиск */
|
||
.controls {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding: 20px;
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
}
|
||
|
||
.search-box {
|
||
flex: 1;
|
||
min-width: 250px;
|
||
position: relative;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
padding: 12px 40px 12px 15px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.search-icon {
|
||
position: absolute;
|
||
right: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: #6c757d;
|
||
}
|
||
|
||
.filter-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-select {
|
||
padding: 10px 15px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
background: white;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.view-toggle {
|
||
display: flex;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.view-btn {
|
||
padding: 8px 12px;
|
||
border: none;
|
||
background: white;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.view-btn.active {
|
||
background: #667eea;
|
||
color: white;
|
||
}
|
||
|
||
/* Сетка изображений */
|
||
.images-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.images-list {
|
||
display: none;
|
||
background: white;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.image-card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
transition: all 0.3s;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.image-card:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.image-preview {
|
||
width: 100%;
|
||
height: 150px;
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-color: #f8f9fa;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.image-preview img {
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.image-placeholder {
|
||
color: #ddd;
|
||
font-size: 48px;
|
||
}
|
||
|
||
.image-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.7);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
opacity: 0;
|
||
transition: opacity 0.3s;
|
||
}
|
||
|
||
.image-card:hover .image-overlay {
|
||
opacity: 1;
|
||
}
|
||
|
||
.overlay-btn {
|
||
padding: 8px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
background: rgba(255,255,255,0.2);
|
||
color: white;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.overlay-btn:hover {
|
||
background: rgba(255,255,255,0.3);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.image-info {
|
||
padding: 15px;
|
||
}
|
||
|
||
.image-name {
|
||
font-weight: 500;
|
||
margin-bottom: 5px;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.image-details {
|
||
font-size: 12px;
|
||
color: #6c757d;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.image-url {
|
||
font-size: 11px;
|
||
color: #6c757d;
|
||
background: #f8f9fa;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-family: monospace;
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* Модальные окна */
|
||
.modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0,0,0,0.8);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.modal.show {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
}
|
||
|
||
.modal-content {
|
||
background: white;
|
||
border-radius: 12px;
|
||
max-width: 90%;
|
||
max-height: 90%;
|
||
overflow: auto;
|
||
position: relative;
|
||
}
|
||
|
||
.modal-header {
|
||
padding: 20px;
|
||
border-bottom: 1px solid #eee;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
color: #6c757d;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 20px;
|
||
}
|
||
|
||
.preview-image {
|
||
max-width: 100%;
|
||
max-height: 70vh;
|
||
object-fit: contain;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
/* Прогресс загрузки */
|
||
.upload-progress {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 100%;
|
||
height: 6px;
|
||
background: #e9ecef;
|
||
border-radius: 3px;
|
||
overflow: hidden;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 3px;
|
||
transition: width 0.3s;
|
||
width: 0%;
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 14px;
|
||
color: #6c757d;
|
||
text-align: center;
|
||
}
|
||
|
||
/* Уведомления */
|
||
.notification {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 15px 20px;
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-weight: 500;
|
||
z-index: 1001;
|
||
transform: translateX(100%);
|
||
transition: transform 0.3s;
|
||
max-width: 400px;
|
||
}
|
||
|
||
.notification.show {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.notification.success {
|
||
background: #28a745;
|
||
}
|
||
|
||
.notification.error {
|
||
background: #dc3545;
|
||
}
|
||
|
||
.notification.info {
|
||
background: #17a2b8;
|
||
}
|
||
|
||
/* Скелетон загрузки */
|
||
.skeleton {
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: loading 1.5s infinite;
|
||
}
|
||
|
||
@keyframes loading {
|
||
0% { background-position: 200% 0; }
|
||
100% { background-position: -200% 0; }
|
||
}
|
||
|
||
.skeleton-card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.skeleton-image {
|
||
width: 100%;
|
||
height: 150px;
|
||
}
|
||
|
||
.skeleton-text {
|
||
height: 16px;
|
||
margin: 15px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.skeleton-text.short {
|
||
width: 60%;
|
||
}
|
||
|
||
/* Адаптив */
|
||
@media (max-width: 768px) {
|
||
.container {
|
||
padding: 15px;
|
||
}
|
||
|
||
.header {
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
text-align: center;
|
||
}
|
||
|
||
.controls {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.filter-group {
|
||
justify-content: center;
|
||
}
|
||
|
||
.images-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<!-- Заголовок -->
|
||
<div class="header">
|
||
<h1>🖼️ Менеджер изображений</h1>
|
||
<div class="header-actions">
|
||
<button class="btn btn-secondary" onclick="refreshGallery()">
|
||
🔄 Обновить
|
||
</button>
|
||
<button class="btn btn-primary" onclick="openUpload()">
|
||
📤 Загрузить изображения
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Область загрузки -->
|
||
<div class="upload-area" id="uploadArea" onclick="openUpload()">
|
||
<div class="upload-icon">📸</div>
|
||
<div class="upload-text">Перетащите изображения сюда или нажмите для выбора</div>
|
||
<div class="upload-subtext">Поддерживаются JPG, PNG, GIF, WEBP до 10MB</div>
|
||
<input type="file" id="fileInput" multiple accept="image/*" style="display: none;" onchange="handleFileUpload(this.files)">
|
||
</div>
|
||
|
||
<!-- Прогресс загрузки -->
|
||
<div class="upload-progress" id="uploadProgress" style="display: none;">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="progressFill"></div>
|
||
</div>
|
||
<div class="progress-text" id="progressText">Загрузка...</div>
|
||
</div>
|
||
|
||
<!-- Контролы -->
|
||
<div class="controls">
|
||
<div class="search-box">
|
||
<input type="text" class="search-input" placeholder="Поиск по названию или типу..."
|
||
oninput="searchImages(this.value)">
|
||
<span class="search-icon">🔍</span>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<select class="filter-select" onchange="filterByType(this.value)">
|
||
<option value="">Все типы</option>
|
||
<option value="routes">Маршруты</option>
|
||
<option value="guides">Гиды</option>
|
||
<option value="articles">Статьи</option>
|
||
<option value="general">Общие</option>
|
||
</select>
|
||
|
||
<div class="view-toggle">
|
||
<button class="view-btn active" onclick="setView('grid')" data-view="grid">⊞</button>
|
||
<button class="view-btn" onclick="setView('list')" data-view="list">≡</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Сетка изображений -->
|
||
<div class="images-grid" id="imagesGrid">
|
||
<!-- Скелетоны загрузки -->
|
||
<div class="skeleton-card">
|
||
<div class="skeleton skeleton-image"></div>
|
||
<div class="skeleton skeleton-text"></div>
|
||
<div class="skeleton skeleton-text short"></div>
|
||
</div>
|
||
<div class="skeleton-card">
|
||
<div class="skeleton skeleton-image"></div>
|
||
<div class="skeleton skeleton-text"></div>
|
||
<div class="skeleton skeleton-text short"></div>
|
||
</div>
|
||
<div class="skeleton-card">
|
||
<div class="skeleton skeleton-image"></div>
|
||
<div class="skeleton skeleton-text"></div>
|
||
<div class="skeleton skeleton-text short"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Список изображений (альтернативный вид) -->
|
||
<div class="images-list" id="imagesList">
|
||
<!-- Будет заполнено динамически -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Модальное окно просмотра -->
|
||
<div class="modal" id="viewModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">Просмотр изображения</h3>
|
||
<button class="close-btn" onclick="closeModal('viewModal')">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<img class="preview-image" id="previewImage" src="" alt="">
|
||
<div style="margin-top: 20px;">
|
||
<p><strong>Имя файла:</strong> <span id="fileName"></span></p>
|
||
<p><strong>Размер:</strong> <span id="fileSize"></span></p>
|
||
<p><strong>URL:</strong> <span id="fileUrl" style="font-family: monospace; background: #f8f9fa; padding: 2px 6px; border-radius: 4px;"></span></p>
|
||
</div>
|
||
<div style="margin-top: 20px; text-align: center;">
|
||
<button class="btn btn-primary" onclick="copyToClipboard()">📋 Копировать URL</button>
|
||
<button class="btn btn-secondary" onclick="downloadImage()">💾 Скачать</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Уведомления -->
|
||
<div id="notification" class="notification"></div>
|
||
|
||
<script>
|
||
let currentImages = [];
|
||
let filteredImages = [];
|
||
let currentView = 'grid';
|
||
let currentImage = null;
|
||
|
||
// Инициализация
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadImages();
|
||
setupDragAndDrop();
|
||
});
|
||
|
||
// Настройка Drag & Drop
|
||
function setupDragAndDrop() {
|
||
const uploadArea = document.getElementById('uploadArea');
|
||
|
||
uploadArea.addEventListener('dragover', function(e) {
|
||
e.preventDefault();
|
||
uploadArea.classList.add('dragover');
|
||
});
|
||
|
||
uploadArea.addEventListener('dragleave', function(e) {
|
||
e.preventDefault();
|
||
uploadArea.classList.remove('dragover');
|
||
});
|
||
|
||
uploadArea.addEventListener('drop', function(e) {
|
||
e.preventDefault();
|
||
uploadArea.classList.remove('dragover');
|
||
handleFileUpload(e.dataTransfer.files);
|
||
});
|
||
}
|
||
|
||
// Загрузка изображений
|
||
async function loadImages() {
|
||
try {
|
||
const response = await fetch('/api/images/gallery');
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
currentImages = data.images || [];
|
||
filteredImages = [...currentImages];
|
||
renderImages();
|
||
} else {
|
||
throw new Error(data.error || 'Ошибка загрузки изображений');
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки изображений:', error);
|
||
showNotification('Ошибка загрузки изображений', 'error');
|
||
currentImages = [];
|
||
filteredImages = [];
|
||
renderEmptyState();
|
||
}
|
||
}
|
||
|
||
// Отображение изображений
|
||
function renderImages() {
|
||
const grid = document.getElementById('imagesGrid');
|
||
const list = document.getElementById('imagesList');
|
||
|
||
if (filteredImages.length === 0) {
|
||
renderEmptyState();
|
||
return;
|
||
}
|
||
|
||
const gridHTML = filteredImages.map(image => createImageCard(image)).join('');
|
||
grid.innerHTML = gridHTML;
|
||
|
||
// Список пока не реализован
|
||
list.innerHTML = '';
|
||
}
|
||
|
||
// Создание карточки изображения
|
||
function createImageCard(image) {
|
||
const fileName = image.path.split('/').pop();
|
||
const fileExtension = fileName.split('.').pop().toUpperCase();
|
||
const fileSize = image.size ? formatFileSize(image.size) : 'Неизвестно';
|
||
|
||
return `
|
||
<div class="image-card" onclick="viewImage('${image.path}', '${fileName}', '${fileSize}')">
|
||
<div class="image-preview" style="background-image: url('${image.path}')">
|
||
<div class="image-overlay">
|
||
<button class="overlay-btn" onclick="event.stopPropagation(); viewImage('${image.path}', '${fileName}', '${fileSize}')" title="Просмотр">👁️</button>
|
||
<button class="overlay-btn" onclick="event.stopPropagation(); copyToClipboard('${image.path}')" title="Копировать URL">📋</button>
|
||
<button class="overlay-btn" onclick="event.stopPropagation(); deleteImage('${image.path}')" title="Удалить">🗑️</button>
|
||
</div>
|
||
</div>
|
||
<div class="image-info">
|
||
<div class="image-name">${fileName}</div>
|
||
<div class="image-details">
|
||
<span>${fileExtension}</span>
|
||
<span>${fileSize}</span>
|
||
</div>
|
||
<div class="image-url">${image.path}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Пустое состояние
|
||
function renderEmptyState() {
|
||
const grid = document.getElementById('imagesGrid');
|
||
grid.innerHTML = `
|
||
<div style="grid-column: 1 / -1; text-align: center; padding: 60px 20px; color: #6c757d;">
|
||
<div style="font-size: 64px; margin-bottom: 20px;">📷</div>
|
||
<h3>Изображения не найдены</h3>
|
||
<p>Загрузите первые изображения или проверьте фильтры поиска</p>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Открытие диалога загрузки
|
||
function openUpload() {
|
||
document.getElementById('fileInput').click();
|
||
}
|
||
|
||
// Обработка загрузки файлов
|
||
async function handleFileUpload(files) {
|
||
if (!files.length) return;
|
||
|
||
const uploadProgress = document.getElementById('uploadProgress');
|
||
const progressFill = document.getElementById('progressFill');
|
||
const progressText = document.getElementById('progressText');
|
||
|
||
uploadProgress.style.display = 'block';
|
||
|
||
for (let i = 0; i < files.length; i++) {
|
||
const file = files[i];
|
||
|
||
// Проверка типа файла
|
||
if (!file.type.startsWith('image/')) {
|
||
showNotification(\`Файл \${file.name} не является изображением\`, 'error');
|
||
continue;
|
||
}
|
||
|
||
// Проверка размера файла (10MB)
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
showNotification(\`Файл \${file.name} слишком большой (максимум 10MB)\`, 'error');
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
const progress = ((i + 1) / files.length) * 100;
|
||
progressFill.style.width = progress + '%';
|
||
progressText.textContent = \`Загрузка \${i + 1} из \${files.length}: \${file.name}\`;
|
||
|
||
const formData = new FormData();
|
||
formData.append('image', file);
|
||
|
||
const response = await fetch('/api/images/upload', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
showNotification(\`Изображение \${file.name} загружено успешно\`, 'success');
|
||
} else {
|
||
throw new Error(result.error || 'Ошибка загрузки');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки:', error);
|
||
showNotification(\`Ошибка загрузки \${file.name}\`, 'error');
|
||
}
|
||
}
|
||
|
||
uploadProgress.style.display = 'none';
|
||
await loadImages(); // Перезагружаем галерею
|
||
}
|
||
|
||
// Поиск изображений
|
||
function searchImages(query) {
|
||
if (!query.trim()) {
|
||
filteredImages = [...currentImages];
|
||
} else {
|
||
const searchTerm = query.toLowerCase();
|
||
filteredImages = currentImages.filter(image => {
|
||
const fileName = image.path.toLowerCase();
|
||
return fileName.includes(searchTerm);
|
||
});
|
||
}
|
||
renderImages();
|
||
}
|
||
|
||
// Фильтрация по типу
|
||
function filterByType(type) {
|
||
if (!type) {
|
||
filteredImages = [...currentImages];
|
||
} else {
|
||
filteredImages = currentImages.filter(image => {
|
||
return image.path.includes(\`/uploads/\${type}/\`) ||
|
||
image.path.includes(\`/\${type}/\`);
|
||
});
|
||
}
|
||
renderImages();
|
||
}
|
||
|
||
// Переключение вида
|
||
function setView(view) {
|
||
currentView = view;
|
||
|
||
// Обновляем кнопки
|
||
document.querySelectorAll('.view-btn').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
document.querySelector(\`[data-view="\${view}"]\`).classList.add('active');
|
||
|
||
// Переключаем отображение
|
||
const grid = document.getElementById('imagesGrid');
|
||
const list = document.getElementById('imagesList');
|
||
|
||
if (view === 'grid') {
|
||
grid.style.display = 'grid';
|
||
list.style.display = 'none';
|
||
} else {
|
||
grid.style.display = 'none';
|
||
list.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
// Просмотр изображения
|
||
function viewImage(path, name, size) {
|
||
currentImage = { path, name, size };
|
||
|
||
document.getElementById('previewImage').src = path;
|
||
document.getElementById('fileName').textContent = name;
|
||
document.getElementById('fileSize').textContent = size;
|
||
document.getElementById('fileUrl').textContent = path;
|
||
|
||
showModal('viewModal');
|
||
}
|
||
|
||
// Копирование URL в буфер обмена
|
||
function copyToClipboard(url = null) {
|
||
const textToCopy = url || currentImage?.path || document.getElementById('fileUrl').textContent;
|
||
|
||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||
showNotification('URL скопирован в буфер обмена', 'success');
|
||
}).catch(() => {
|
||
// Fallback для старых браузеров
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = textToCopy;
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textArea);
|
||
showNotification('URL скопирован в буфер обмена', 'success');
|
||
});
|
||
}
|
||
|
||
// Скачивание изображения
|
||
function downloadImage() {
|
||
if (currentImage) {
|
||
const link = document.createElement('a');
|
||
link.href = currentImage.path;
|
||
link.download = currentImage.name;
|
||
link.click();
|
||
}
|
||
}
|
||
|
||
// Удаление изображения
|
||
async function deleteImage(path) {
|
||
if (!confirm('Вы уверены, что хотите удалить это изображение?')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/images/delete', {
|
||
method: 'DELETE',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ path })
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
showNotification('Изображение удалено', 'success');
|
||
await loadImages();
|
||
} else {
|
||
throw new Error(result.error || 'Ошибка удаления');
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка удаления:', error);
|
||
showNotification('Ошибка удаления изображения', 'error');
|
||
}
|
||
}
|
||
|
||
// Обновление галереи
|
||
async function refreshGallery() {
|
||
showNotification('Обновление галереи...', 'info');
|
||
await loadImages();
|
||
}
|
||
|
||
// Показ модального окна
|
||
function showModal(modalId) {
|
||
document.getElementById(modalId).classList.add('show');
|
||
}
|
||
|
||
// Закрытие модального окна
|
||
function closeModal(modalId) {
|
||
document.getElementById(modalId).classList.remove('show');
|
||
}
|
||
|
||
// Показ уведомлений
|
||
function showNotification(message, type = 'info') {
|
||
const notification = document.getElementById('notification');
|
||
notification.textContent = message;
|
||
notification.className = \`notification \${type} show\`;
|
||
|
||
setTimeout(() => {
|
||
notification.classList.remove('show');
|
||
}, 3000);
|
||
}
|
||
|
||
// Форматирование размера файла
|
||
function formatFileSize(bytes) {
|
||
if (bytes === 0) return '0 Bytes';
|
||
const k = 1024;
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
}
|
||
|
||
// Закрытие модального окна по клику на overlay
|
||
document.addEventListener('click', function(e) {
|
||
if (e.target.classList.contains('modal')) {
|
||
e.target.classList.remove('show');
|
||
}
|
||
});
|
||
|
||
// Горячие клавиши
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
document.querySelectorAll('.modal.show').forEach(modal => {
|
||
modal.classList.remove('show');
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |